From b8dd0890b3dbc5d3c1b71a0f5de026b46993a851 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 12:40:31 +0200 Subject: [PATCH 01/23] params: begin v1.9.17 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index d222f6c979..5fb0e1fd9f 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 16 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 17 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 6eef141aef618afa6bfad885b544f25b68f77cc2 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 13 Jul 2020 17:02:54 +0800 Subject: [PATCH 02/23] les: historical data garbage collection (#19570) This change introduces garbage collection for the light client. Historical chain data is deleted periodically. If you want to disable the GC, use the --light.nopruning flag. --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 18 ++- consensus/clique/clique.go | 2 +- core/blockchain.go | 8 +- core/chain_indexer.go | 9 +- core/chain_indexer_test.go | 4 + core/chain_makers.go | 2 +- core/dao_test.go | 8 +- core/genesis.go | 2 +- core/rawdb/accessors_chain.go | 33 +++++ core/rawdb/accessors_chain_test.go | 33 +++++ core/rawdb/accessors_indexes.go | 22 +++ core/rawdb/accessors_indexes_test.go | 45 ++++++ core/rawdb/freezer.go | 8 +- core/state/statedb_test.go | 6 +- eth/api_backend.go | 4 +- eth/bloombits.go | 5 + eth/config.go | 9 +- eth/downloader/downloader.go | 20 ++- eth/downloader/downloader_test.go | 3 +- eth/downloader/testchain_test.go | 2 +- eth/gen_config.go | 6 + graphql/graphql.go | 2 +- internal/ethapi/api.go | 25 ++-- internal/ethapi/backend.go | 2 +- les/api_backend.go | 7 +- les/client.go | 9 +- les/odr_requests.go | 10 +- les/odr_test.go | 2 +- les/pruner.go | 98 +++++++++++++ les/pruner_test.go | 197 +++++++++++++++++++++++++ les/request_test.go | 2 +- les/server.go | 4 +- les/sync_test.go | 4 +- les/test_helper.go | 16 +- les/ulc_test.go | 2 +- light/lightchain.go | 20 ++- light/odr.go | 9 +- light/odr_util.go | 212 ++++++++++++++------------- light/postprocess.go | 159 +++++++++++++++++--- params/network_params.go | 10 +- trie/database.go | 11 +- trie/iterator_test.go | 4 +- trie/trie_test.go | 2 +- 45 files changed, 843 insertions(+), 215 deletions(-) create mode 100644 les/pruner.go create mode 100644 les/pruner_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 03ac7bee5e..e82e0ec540 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -98,6 +98,7 @@ var ( utils.LightEgressFlag, utils.LightMaxPeersFlag, utils.LegacyLightPeersFlag, + utils.LightNoPruneFlag, utils.LightKDFFlag, utils.UltraLightServersFlag, utils.UltraLightFractionFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index ee97e1a972..05bd281308 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -96,6 +96,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.UltraLightServersFlag, utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, + utils.LightNoPruneFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8ed86d2fa2..c5e2aa8d65 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -282,6 +282,10 @@ var ( Name: "ulc.onlyannounce", Usage: "Ultra light server sends announcements only", } + LightNoPruneFlag = cli.BoolFlag{ + Name: "light.nopruning", + Usage: "Disable ancient light chain data pruning", + } // Ethash settings EthashCacheDirFlag = DirectoryFlag{ Name: "ethash.cachedir", @@ -1070,6 +1074,9 @@ func setLes(ctx *cli.Context, cfg *eth.Config) { if ctx.GlobalIsSet(UltraLightOnlyAnnounceFlag.Name) { cfg.UltraLightOnlyAnnounce = ctx.GlobalBool(UltraLightOnlyAnnounceFlag.Name) } + if ctx.GlobalIsSet(LightNoPruneFlag.Name) { + cfg.LightNoPrune = ctx.GlobalBool(LightNoPruneFlag.Name) + } } // makeDatabaseHandles raises out the number of allowed file handles per process @@ -1800,12 +1807,17 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = makeDatabaseHandles() + + err error + chainDb ethdb.Database ) - name := "chaindata" if ctx.GlobalString(SyncModeFlag.Name) == "light" { - name = "lightchaindata" + name := "lightchaindata" + chainDb, err = stack.OpenDatabase(name, cache, handles, "") + } else { + name := "chaindata" + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "") } - chainDb, err := stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "") if err != nil { Fatalf("Could not open database: %v", err) } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 1745575318..35542baf4e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -369,7 +369,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo // at a checkpoint block without a parent (light client CHT), or we have piled // up more headers than allowed to be reorged (chain reinit from a freezer), // consider the checkpoint trusted and snapshot it. - if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.ImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) { + if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) { checkpoint := chain.GetHeaderByNumber(number) if checkpoint != nil { hash := checkpoint.Hash() diff --git a/core/blockchain.go b/core/blockchain.go index 0987d65be8..7742f4ec28 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -901,14 +901,14 @@ func (bc *BlockChain) Stop() { recent := bc.GetBlockByNumber(number - offset) log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root()) - if err := triedb.Commit(recent.Root(), true); err != nil { + if err := triedb.Commit(recent.Root(), true, nil); err != nil { log.Error("Failed to commit recent state trie", "err", err) } } } if snapBase != (common.Hash{}) { log.Info("Writing snapshot state to disk", "root", snapBase) - if err := triedb.Commit(snapBase, true); err != nil { + if err := triedb.Commit(snapBase, true, nil); err != nil { log.Error("Failed to commit recent state trie", "err", err) } } @@ -1442,7 +1442,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // If we're running an archive node, always flush if bc.cacheConfig.TrieDirtyDisabled { - if err := triedb.Commit(root, false); err != nil { + if err := triedb.Commit(root, false, nil); err != nil { return NonStatTy, err } } else { @@ -1476,7 +1476,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/TriesInMemory) } // Flush an entire trie and restart the counters - triedb.Commit(header.Root, true) + triedb.Commit(header.Root, true, nil) lastWrite = chosen bc.gcproc = 0 } diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 1bff3aee74..066bca1000 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -46,6 +46,9 @@ type ChainIndexerBackend interface { // Commit finalizes the section metadata and stores it into the database. Commit() error + + // Prune deletes the chain index older than the given threshold. + Prune(threshold uint64) error } // ChainIndexerChain interface is used for connecting the indexer to a blockchain @@ -386,7 +389,6 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com c.log.Trace("Processing new chain section", "section", section) // Reset and partial processing - if err := c.backend.Reset(c.ctx, section, lastHead); err != nil { c.setValidSections(0) return common.Hash{}, err @@ -459,6 +461,11 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) { } } +// Prune deletes all chain data older than given threshold. +func (c *ChainIndexer) Prune(threshold uint64) error { + return c.backend.Prune(threshold) +} + // loadValidSections reads the number of valid sections from the index database // and caches is into the local state. func (c *ChainIndexer) loadValidSections() { diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index ff7548e7bd..b76203dc8f 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -236,3 +236,7 @@ func (b *testChainIndexBackend) Commit() error { } return nil } + +func (b *testChainIndexBackend) Prune(threshold uint64) error { + return nil +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 6524087d4e..33f253d9e8 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -220,7 +220,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } - if err := statedb.Database().TrieDB().Commit(root, false); err != nil { + if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil { panic(fmt.Sprintf("trie write error: %v", err)) } return block, b.receipts diff --git a/core/dao_test.go b/core/dao_test.go index 89e1d83d7a..b2a9f624a7 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -79,7 +79,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import contra-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil { + if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil { t.Fatalf("failed to commit contra-fork head for expansion: %v", err) } blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) @@ -104,7 +104,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import pro-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil { + if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil { t.Fatalf("failed to commit pro-fork head for expansion: %v", err) } blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) @@ -130,7 +130,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import contra-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil { + if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil { t.Fatalf("failed to commit contra-fork head for expansion: %v", err) } blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) @@ -150,7 +150,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import pro-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil { + if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil { t.Fatalf("failed to commit pro-fork head for expansion: %v", err) } blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) diff --git a/core/genesis.go b/core/genesis.go index 655736906f..afaa29c428 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -285,7 +285,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { head.Difficulty = params.GenesisDifficulty } statedb.Commit(false) - statedb.Database().TrieDB().Commit(root, true) + statedb.Database().TrieDB().Commit(root, true, nil) return types.NewBlock(head, nil, nil, nil) } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 2290e87d52..8dd1f6345a 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -80,6 +80,39 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash { return hashes } +// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the +// certain chain range. If the accumulated entries reaches the given threshold, +// abort the iteration and return the semi-finish result. +func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int) ([]uint64, []common.Hash) { + // Short circuit if the limit is 0. + if limit == 0 { + return nil, nil + } + var ( + numbers []uint64 + hashes []common.Hash + ) + // Construct the key prefix of start point. + start, end := headerHashKey(from), headerHashKey(to) + it := db.NewIterator(nil, start) + defer it.Release() + + for it.Next() { + if bytes.Compare(it.Key(), end) >= 0 { + break + } + if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) { + numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8])) + hashes = append(hashes, common.BytesToHash(it.Value())) + // If the accumulated entries reaches the limit threshold, return. + if len(numbers) >= limit { + break + } + } + } + return numbers, hashes +} + // ReadHeaderNumber returns the header number assigned to a hash. func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 { data, _ := db.Get(headerNumberKey(hash)) diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index bb7dad5dff..3eba2a3b4e 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "math/big" "os" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -424,3 +425,35 @@ func TestAncientStorage(t *testing.T) { t.Fatalf("invalid td returned") } } + +func TestCanonicalHashIteration(t *testing.T) { + var cases = []struct { + from, to uint64 + limit int + expect []uint64 + }{ + {1, 8, 0, nil}, + {1, 8, 1, []uint64{1}}, + {1, 8, 10, []uint64{1, 2, 3, 4, 5, 6, 7}}, + {1, 9, 10, []uint64{1, 2, 3, 4, 5, 6, 7, 8}}, + {2, 9, 10, []uint64{2, 3, 4, 5, 6, 7, 8}}, + {9, 10, 10, nil}, + } + // Test empty db iteration + db := NewMemoryDatabase() + numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10) + if len(numbers) != 0 { + t.Fatalf("No entry should be returned to iterate an empty db") + } + // Fill database with testing data. + for i := uint64(1); i <= 8; i++ { + WriteCanonicalHash(db, common.Hash{}, i) + WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data + } + for i, c := range cases { + numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit) + if !reflect.DeepEqual(numbers, c.expect) { + t.Fatalf("Case %d failed, want %v, got %v", i, c.expect, numbers) + } + } +} diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index c7f3df2ad7..9a05eba8d6 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -17,6 +17,7 @@ package rawdb import ( + "bytes" "math/big" "github.com/ethereum/go-ethereum/common" @@ -151,3 +152,24 @@ func WriteBloomBits(db ethdb.KeyValueWriter, bit uint, section uint64, head comm log.Crit("Failed to store bloom bits", "err", err) } } + +// DeleteBloombits removes all compressed bloom bits vector belonging to the +// given section range and bit index. +func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) { + start, end := bloomBitsKey(bit, from, common.Hash{}), bloomBitsKey(bit, to, common.Hash{}) + it := db.NewIterator(nil, start) + defer it.Release() + + for it.Next() { + if bytes.Compare(it.Key(), end) >= 0 { + break + } + if len(it.Key()) != len(bloomBitsPrefix)+2+8+32 { + continue + } + db.Delete(it.Key()) + } + if it.Error() != nil { + log.Crit("Failed to delete bloom bits", "err", it.Error()) + } +} diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index c09bff0101..49d00f9900 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -17,12 +17,14 @@ package rawdb import ( + "bytes" "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" ) @@ -106,3 +108,46 @@ func TestLookupStorage(t *testing.T) { }) } } + +func TestDeleteBloomBits(t *testing.T) { + // Prepare testing data + db := NewMemoryDatabase() + for i := uint(0); i < 2; i++ { + for s := uint64(0); s < 2; s++ { + WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02}) + WriteBloomBits(db, i, s, params.RinkebyGenesisHash, []byte{0x01, 0x02}) + } + } + check := func(bit uint, section uint64, head common.Hash, exist bool) { + bits, _ := ReadBloomBits(db, bit, section, head) + if exist && !bytes.Equal(bits, []byte{0x01, 0x02}) { + t.Fatalf("Bloombits mismatch") + } + if !exist && len(bits) > 0 { + t.Fatalf("Bloombits should be removed") + } + } + // Check the existence of written data. + check(0, 0, params.MainnetGenesisHash, true) + check(0, 0, params.RinkebyGenesisHash, true) + + // Check the existence of deleted data. + DeleteBloombits(db, 0, 0, 1) + check(0, 0, params.MainnetGenesisHash, false) + check(0, 0, params.RinkebyGenesisHash, false) + check(0, 1, params.MainnetGenesisHash, true) + check(0, 1, params.RinkebyGenesisHash, true) + + // Check the existence of deleted data. + DeleteBloombits(db, 0, 0, 2) + check(0, 0, params.MainnetGenesisHash, false) + check(0, 0, params.RinkebyGenesisHash, false) + check(0, 1, params.MainnetGenesisHash, false) + check(0, 1, params.RinkebyGenesisHash, false) + + // Bit1 shouldn't be affect. + check(1, 0, params.MainnetGenesisHash, true) + check(1, 0, params.RinkebyGenesisHash, true) + check(1, 1, params.MainnetGenesisHash, true) + check(1, 1, params.RinkebyGenesisHash, true) +} diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 01ad281ac1..9a40b2cf43 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -287,12 +287,12 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { backoff = true continue - case *number < params.ImmutabilityThreshold: - log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold) + case *number < params.FullImmutabilityThreshold: + log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.FullImmutabilityThreshold) backoff = true continue - case *number-params.ImmutabilityThreshold <= f.frozen: + case *number-params.FullImmutabilityThreshold <= f.frozen: log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) backoff = true continue @@ -304,7 +304,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { continue } // Seems we have data ready to be frozen, process in usable batches - limit := *number - params.ImmutabilityThreshold + limit := *number - params.FullImmutabilityThreshold if limit-f.frozen > freezerBatchLimit { limit = f.frozen + freezerBatchLimit } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index be24635355..824a597498 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -55,7 +55,7 @@ func TestUpdateLeaks(t *testing.T) { } root := state.IntermediateRoot(false) - if err := state.Database().TrieDB().Commit(root, false); err != nil { + if err := state.Database().TrieDB().Commit(root, false, nil); err != nil { t.Errorf("can not commit trie %v to persistent database", root.Hex()) } @@ -106,7 +106,7 @@ func TestIntermediateLeaks(t *testing.T) { if err != nil { t.Fatalf("failed to commit transition state: %v", err) } - if err = transState.Database().TrieDB().Commit(transRoot, false); err != nil { + if err = transState.Database().TrieDB().Commit(transRoot, false, nil); err != nil { t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) } @@ -114,7 +114,7 @@ func TestIntermediateLeaks(t *testing.T) { if err != nil { t.Fatalf("failed to commit final state: %v", err) } - if err = finalState.Database().TrieDB().Commit(finalRoot, false); err != nil { + if err = finalState.Database().TrieDB().Commit(finalRoot, false, nil); err != nil { t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) } diff --git a/eth/api_backend.go b/eth/api_backend.go index 60ad37e68b..a7122da2cb 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -185,8 +185,8 @@ func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*typ return logs, nil } -func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { - return b.eth.blockchain.GetTdByHash(blockHash) +func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + return b.eth.blockchain.GetTdByHash(hash) } func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { diff --git a/eth/bloombits.go b/eth/bloombits.go index 35522b9bfa..f8b77f9cff 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -136,3 +136,8 @@ func (b *BloomIndexer) Commit() error { } return batch.Write() } + +// PruneSections returns an empty error since we don't support pruning here. +func (b *BloomIndexer) Prune(threshold uint64) error { + return nil +} diff --git a/eth/config.go b/eth/config.go index 8547ac1772..8b9651e09b 100644 --- a/eth/config.go +++ b/eth/config.go @@ -122,10 +122,11 @@ type Config struct { Whitelist map[uint64]common.Hash `toml:"-"` // Light client options - LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests - LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers - LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers - LightPeers int `toml:",omitempty"` // Maximum number of LES client peers + LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests + LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers + LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers + LightPeers int `toml:",omitempty"` // Maximum number of LES client peers + LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning // Ultra Light client options UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 9e7ea947fa..3a289a9c7f 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -42,7 +42,6 @@ var ( MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly - MaxBodyFetch = 128 // Amount of block bodies to be fetched per retrieval request MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request MaxStateFetch = 384 // Amount of node state values to allow fetching per request @@ -56,10 +55,11 @@ var ( qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value - maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) - maxHeadersProcess = 2048 // Number of header download results to import at once into the chain - maxResultsProcess = 2048 // Number of content download results to import at once into the chain - maxForkAncestry uint64 = params.ImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) + maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) + maxHeadersProcess = 2048 // Number of header download results to import at once into the chain + maxResultsProcess = 2048 // Number of content download results to import at once into the chain + fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) + lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs @@ -490,10 +490,10 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I // The peer would start to feed us valid blocks until head, resulting in all of // the blocks might be written into the ancient store. A following mini-reorg // could cause issues. - if d.checkpoint != 0 && d.checkpoint > maxForkAncestry+1 { + if d.checkpoint != 0 && d.checkpoint > fullMaxForkAncestry+1 { d.ancientLimit = d.checkpoint - } else if height > maxForkAncestry+1 { - d.ancientLimit = height - maxForkAncestry - 1 + } else if height > fullMaxForkAncestry+1 { + d.ancientLimit = height - fullMaxForkAncestry - 1 } frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. // If a part of blockchain data has already been written into active store, @@ -727,6 +727,10 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) // Recap floor value for binary search + maxForkAncestry := fullMaxForkAncestry + if d.getMode() == LightSync { + maxForkAncestry = lightMaxForkAncestry + } if localHeight >= maxForkAncestry { // We're above the max reorg threshold, find the earliest fork point floor = int64(localHeight - maxForkAncestry) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 4750da54d2..f9092175cb 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -37,7 +37,8 @@ import ( // Reduce some of the parameters to make the tester faster. func init() { - maxForkAncestry = 10000 + fullMaxForkAncestry = 10000 + lightMaxForkAncestry = 10000 blockCacheItems = 1024 fsHeaderContCheck = 500 * time.Millisecond } diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index f410152f5b..26b6b6a460 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -45,7 +45,7 @@ var testChainBase = newTestChain(blockCacheItems+200, testGenesis) var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain func init() { - var forkLen = int(maxForkAncestry + 50) + var forkLen = int(fullMaxForkAncestry + 50) var wg sync.WaitGroup wg.Add(3) go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() diff --git a/eth/gen_config.go b/eth/gen_config.go index 8f4a8fa944..2defc36cb2 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -29,6 +29,7 @@ func (c Config) MarshalTOML() (interface{}, error) { LightIngress int `toml:",omitempty"` LightEgress int `toml:",omitempty"` LightPeers int `toml:",omitempty"` + LightNoPrune bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction int `toml:",omitempty"` UltraLightOnlyAnnounce bool `toml:",omitempty"` @@ -66,6 +67,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.LightIngress = c.LightIngress enc.LightEgress = c.LightEgress enc.LightPeers = c.LightPeers + enc.LightNoPrune = c.LightNoPrune enc.UltraLightServers = c.UltraLightServers enc.UltraLightFraction = c.UltraLightFraction enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce @@ -107,6 +109,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { LightIngress *int `toml:",omitempty"` LightEgress *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` + LightNoPrune *bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction *int `toml:",omitempty"` UltraLightOnlyAnnounce *bool `toml:",omitempty"` @@ -171,6 +174,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.LightPeers != nil { c.LightPeers = *dec.LightPeers } + if dec.LightNoPrune != nil { + c.LightNoPrune = *dec.LightNoPrune + } if dec.UltraLightServers != nil { c.UltraLightServers = dec.UltraLightServers } diff --git a/graphql/graphql.go b/graphql/graphql.go index 6e29ccc6eb..1479ae7fdb 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -584,7 +584,7 @@ func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { } h = header.Hash() } - return hexutil.Big(*b.backend.GetTd(h)), nil + return hexutil.Big(*b.backend.GetTd(ctx, h)), nil } // BlockNumberArgs encapsulates arguments to accessors that specify a block number. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 99b94bd5c1..bd4ea6fdd4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -36,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -625,7 +624,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { - response := s.rpcMarshalHeader(header) + response := s.rpcMarshalHeader(ctx, header) if number == rpc.PendingBlockNumber { // Pending header need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { @@ -641,7 +640,7 @@ func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc. func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} { header, _ := s.b.HeaderByHash(ctx, hash) if header != nil { - return s.rpcMarshalHeader(header) + return s.rpcMarshalHeader(ctx, header) } return nil } @@ -654,7 +653,7 @@ func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.H func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { - response, err := s.rpcMarshalBlock(block, true, fullTx) + response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) if err == nil && number == rpc.PendingBlockNumber { // Pending blocks need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { @@ -671,7 +670,7 @@ func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.B func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByHash(ctx, hash) if block != nil { - return s.rpcMarshalBlock(block, true, fullTx) + return s.rpcMarshalBlock(ctx, block, true, fullTx) } return nil, err } @@ -687,7 +686,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, return nil, nil } block = types.NewBlockWithHeader(uncles[index]) - return s.rpcMarshalBlock(block, false, false) + return s.rpcMarshalBlock(ctx, block, false, false) } return nil, err } @@ -703,7 +702,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, b return nil, nil } block = types.NewBlockWithHeader(uncles[index]) - return s.rpcMarshalBlock(block, false, false) + return s.rpcMarshalBlock(ctx, block, false, false) } return nil, err } @@ -1173,21 +1172,21 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]i // rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires // a `PublicBlockchainAPI`. -func (s *PublicBlockChainAPI) rpcMarshalHeader(header *types.Header) map[string]interface{} { +func (s *PublicBlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} { fields := RPCMarshalHeader(header) - fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(header.Hash())) + fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash())) return fields } // rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires // a `PublicBlockchainAPI`. -func (s *PublicBlockChainAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { +func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { fields, err := RPCMarshalBlock(b, inclTx, fullTx) if err != nil { return nil, err } if inclTx { - fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash())) + fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash())) } return fields, err } @@ -1393,8 +1392,8 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, // GetTransactionReceipt returns the transaction receipt for the given transaction hash. func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash) - if tx == nil { + tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash) + if err != nil { return nil, nil } receipts, err := s.b.GetReceipts(ctx, blockHash) diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index dbdd35ac70..074cd794a6 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -59,7 +59,7 @@ type Backend interface { StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) - GetTd(hash common.Hash) *big.Int + GetTd(ctx context.Context, hash common.Hash) *big.Int GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription diff --git a/les/api_backend.go b/les/api_backend.go index f72cbba073..448260a198 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -162,8 +162,11 @@ func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*typ return nil, nil } -func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { - return b.eth.blockchain.GetTdByHash(hash) +func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { + return b.eth.blockchain.GetTdOdr(ctx, hash, *number) + } + return nil } func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { diff --git a/les/client.go b/les/client.go index 34a654e22d..49b21c9673 100644 --- a/les/client.go +++ b/les/client.go @@ -62,6 +62,7 @@ type LightEthereum struct { serverPool *serverPool valueTracker *lpc.ValueTracker dialCandidates enode.Iterator + pruner *pruner bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -121,8 +122,8 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { leth.relay = newLesTxRelay(peers, leth.retriever) leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever) - leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations) - leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency) + leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune) + leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune) leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) checkpoint := config.Checkpoint @@ -149,6 +150,9 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { leth.chtIndexer.Start(leth.blockchain) leth.bloomIndexer.Start(leth.blockchain) + // Start a light chain pruner to delete useless historical data. + leth.pruner = newPruner(chainDb, leth.chtIndexer, leth.bloomTrieIndexer) + // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) @@ -302,6 +306,7 @@ func (s *LightEthereum) Stop() error { s.handler.stop() s.txPool.Stop() s.engine.Close() + s.pruner.close() s.eventMux.Stop() s.chainDb.Close() s.wg.Wait() diff --git a/les/odr_requests.go b/les/odr_requests.go index c4b38060cc..8c1e0102f5 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -110,14 +110,16 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { body := bodies[0] // Retrieve our stored header and validate block content against it - header := rawdb.ReadHeader(db, r.Hash, r.Number) - if header == nil { + if r.Header == nil { + r.Header = rawdb.ReadHeader(db, r.Hash, r.Number) + } + if r.Header == nil { return errHeaderUnavailable } - if header.TxHash != types.DeriveSha(types.Transactions(body.Transactions)) { + if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions)) { return errTxHashMismatch } - if header.UncleHash != types.CalcUncleHash(body.Uncles) { + if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) { return errUncleHashMismatch } // Validations passed, encode and store RLP diff --git a/les/odr_test.go b/les/odr_test.go index 01cc956953..d30642c4f7 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -183,7 +183,7 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon // testOdr tests odr requests whose validation guaranteed by block headers. func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true) + server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) defer tearDown() // Ensure the client has synced all necessary data. diff --git a/les/pruner.go b/les/pruner.go new file mode 100644 index 0000000000..622e648688 --- /dev/null +++ b/les/pruner.go @@ -0,0 +1,98 @@ +// Copyright 2019 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 . + +package les + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// pruner is responsible for pruning historical light chain data. +type pruner struct { + db ethdb.Database + indexers []*core.ChainIndexer + closeCh chan struct{} + wg sync.WaitGroup +} + +// newPruner returns a light chain pruner instance. +func newPruner(db ethdb.Database, indexers ...*core.ChainIndexer) *pruner { + pruner := &pruner{ + db: db, + indexers: indexers, + closeCh: make(chan struct{}), + } + pruner.wg.Add(1) + go pruner.loop() + return pruner +} + +// close notifies all background goroutines belonging to pruner to exit. +func (p *pruner) close() { + close(p.closeCh) + p.wg.Wait() +} + +// loop periodically queries the status of chain indexers and prunes useless +// historical chain data. Notably, whenever Geth restarts, it will iterate +// all historical sections even they don't exist at all(below checkpoint) so +// that light client can prune cached chain data that was ODRed after pruning +// that section. +func (p *pruner) loop() { + defer p.wg.Done() + + // cleanTicker is the ticker used to trigger a history clean 2 times a day. + var cleanTicker = time.NewTicker(12 * time.Hour) + + // pruning finds the sections that have been processed by all indexers + // and deletes all historical chain data. + // Note, if some indexers don't support pruning(e.g. eth.BloomIndexer), + // pruning operations can be silently ignored. + pruning := func() { + min := uint64(math.MaxUint64) + for _, indexer := range p.indexers { + sections, _, _ := indexer.Sections() + if sections < min { + min = sections + } + } + // Always keep the latest section data in database. + if min < 2 || len(p.indexers) == 0 { + return + } + for _, indexer := range p.indexers { + if err := indexer.Prune(min - 2); err != nil { + log.Debug("Failed to prune historical data", "err", err) + return + } + } + p.db.Compact(nil, nil) // Compact entire database, ensure all removed data are deleted. + } + for { + pruning() + select { + case <-cleanTicker.C: + case <-p.closeCh: + return + } + } +} diff --git a/les/pruner_test.go b/les/pruner_test.go new file mode 100644 index 0000000000..62b4e9a950 --- /dev/null +++ b/les/pruner_test.go @@ -0,0 +1,197 @@ +// Copyright 2019 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 . + +package les + +import ( + "bytes" + "context" + "encoding/binary" + "testing" + "time" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/light" +) + +func TestLightPruner(t *testing.T) { + config := light.TestClientIndexerConfig + + waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 3 && bts >= 3 { + break + } + time.Sleep(10 * time.Millisecond) + } + } + server, client, tearDown := newClientServerEnv(t, int(3*config.ChtSize+config.ChtConfirms), 2, waitIndexers, nil, 0, false, true, false) + defer tearDown() + + // checkDB iterates the chain with given prefix, resolves the block number + // with given callback and ensures this entry should exist or not. + checkDB := func(from, to uint64, prefix []byte, resolve func(key, value []byte) *uint64, exist bool) bool { + it := client.db.NewIterator(prefix, nil) + defer it.Release() + + var next = from + for it.Next() { + number := resolve(it.Key(), it.Value()) + if number == nil || *number < from { + continue + } else if *number > to { + return true + } + if exist { + if *number != next { + return false + } + next++ + } else { + return false + } + } + return true + } + // checkPruned checks and ensures the stale chain data has been pruned. + checkPruned := func(from, to uint64) { + // Iterate canonical hash + if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 { + if len(key) == 1+8+1 && bytes.Equal(key[9:10], []byte("n")) { + n := binary.BigEndian.Uint64(key[1:9]) + return &n + } + return nil + }, false) { + t.Fatalf("canonical hash mappings are not properly pruned") + } + // Iterate header + if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 { + if len(key) == 1+8+32 { + n := binary.BigEndian.Uint64(key[1:9]) + return &n + } + return nil + }, false) { + t.Fatalf("headers are not properly pruned") + } + // Iterate body + if !checkDB(from, to, []byte("b"), func(key, value []byte) *uint64 { + if len(key) == 1+8+32 { + n := binary.BigEndian.Uint64(key[1:9]) + return &n + } + return nil + }, false) { + t.Fatalf("block bodies are not properly pruned") + } + // Iterate receipts + if !checkDB(from, to, []byte("r"), func(key, value []byte) *uint64 { + if len(key) == 1+8+32 { + n := binary.BigEndian.Uint64(key[1:9]) + return &n + } + return nil + }, false) { + t.Fatalf("receipts are not properly pruned") + } + // Iterate td + if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 { + if len(key) == 1+8+32+1 && bytes.Equal(key[41:42], []byte("t")) { + n := binary.BigEndian.Uint64(key[1:9]) + return &n + } + return nil + }, false) { + t.Fatalf("tds are not properly pruned") + } + } + // Start light pruner. + time.Sleep(1500 * time.Millisecond) // Ensure light client has finished the syncing and indexing + newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer) + + time.Sleep(1500 * time.Millisecond) // Ensure pruner have enough time to prune data. + checkPruned(1, config.ChtSize-1) + + // Ensure all APIs still work after pruning. + var cases = []struct { + from, to uint64 + methodName string + method func(uint64) bool + }{ + { + 1, 10, "GetHeaderByNumber", + func(n uint64) bool { + _, err := light.GetHeaderByNumber(context.Background(), client.handler.backend.odr, n) + return err == nil + }, + }, + { + 11, 20, "GetCanonicalHash", + func(n uint64) bool { + _, err := light.GetCanonicalHash(context.Background(), client.handler.backend.odr, n) + return err == nil + }, + }, + { + 21, 30, "GetTd", + func(n uint64) bool { + _, err := light.GetTd(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) + return err == nil + }, + }, + { + 31, 40, "GetBodyRLP", + func(n uint64) bool { + _, err := light.GetBodyRLP(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) + return err == nil + }, + }, + { + 41, 50, "GetBlock", + func(n uint64) bool { + _, err := light.GetBlock(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) + return err == nil + }, + }, + { + 51, 60, "GetBlockReceipts", + func(n uint64) bool { + _, err := light.GetBlockReceipts(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) + return err == nil + }, + }, + } + for _, c := range cases { + for i := c.from; i <= c.to; i++ { + if !c.method(i) { + t.Fatalf("rpc method %s failed, number %d", c.methodName, i) + } + } + } + // Check GetBloombits + _, err := light.GetBloomBits(context.Background(), client.handler.backend.odr, 0, []uint64{0}) + if err != nil { + t.Fatalf("Failed to retrieve bloombits of pruned section: %v", err) + } + + // Ensure the ODR cached data can be cleaned by pruner. + newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer) + time.Sleep(50 * time.Millisecond) // Ensure pruner have enough time to prune data. + checkPruned(1, config.ChtSize-1) // Ensure all cached data(by odr) is cleaned. +} diff --git a/les/request_test.go b/les/request_test.go index e20b06fda5..4851274382 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -79,7 +79,7 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq func testAccess(t *testing.T, protocol int, fn accessTestFn) { // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true) + server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) defer tearDown() // Ensure the client has synced all necessary data. diff --git a/les/server.go b/les/server.go index 4b623f61ed..a154571b4f 100644 --- a/les/server.go +++ b/les/server.go @@ -77,8 +77,8 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { iConfig: light.DefaultServerIndexerConfig, chainDb: e.ChainDb(), chainReader: e.BlockChain(), - chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations), - bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency), + chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true), + bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true), closeCh: make(chan struct{}), }, archiveMode: e.ArchiveMode(), diff --git a/les/sync_test.go b/les/sync_test.go index c128a8c9f7..ffce4d8df2 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -54,7 +54,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } } // Generate 512+4 blocks (totally 1 CHT sections) - server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false) + server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) defer tearDown() expected := config.ChtSize + config.ChtConfirms @@ -144,7 +144,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } } // Generate 512+4 blocks (totally 1 CHT sections) - server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false) + server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) defer tearDown() expected := config.ChtSize + config.ChtConfirms diff --git a/les/test_helper.go b/les/test_helper.go index 2a2bbb440e..28906f1f10 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -158,11 +158,11 @@ func prepare(n int, backend *backends.SimulatedBackend) { } // testIndexers creates a set of indexers with specified params for testing purpose. -func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig) []*core.ChainIndexer { +func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig, disablePruning bool) []*core.ChainIndexer { var indexers [3]*core.ChainIndexer - indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms) + indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms, disablePruning) indexers[1] = eth.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms) - indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize) + indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize, disablePruning) // make bloomTrieIndexer as a child indexer of bloom indexer. indexers[1].AddChildIndexer(indexers[2]) return indexers[:] @@ -456,7 +456,7 @@ type testServer struct { func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64) (*testServer, func()) { db := rawdb.NewMemoryDatabase() - indexers := testIndexers(db, nil, light.TestServerIndexerConfig) + indexers := testIndexers(db, nil, light.TestServerIndexerConfig, true) var clock mclock.Clock = &mclock.System{} if simClock { @@ -499,7 +499,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallba return server, teardown } -func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool) (*testServer, *testClient, func()) { +func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool, disablePruning bool) (*testServer, *testClient, func()) { sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase() speers, cpeers := newServerPeerSet(), newClientPeerSet() @@ -511,8 +511,8 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 }) odr := NewLesOdr(cdb, light.TestClientIndexerConfig, rm) - sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig) - cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig) + sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true) + cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, disablePruning) scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2] ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2] @@ -542,7 +542,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer } select { case <-done: - case <-time.After(3 * time.Second): + case <-time.After(10 * time.Second): t.Fatal("test peer did not connect and sync within 3s") } } diff --git a/les/ulc_test.go b/les/ulc_test.go index 273c63e4bd..657b13db2c 100644 --- a/les/ulc_test.go +++ b/les/ulc_test.go @@ -138,6 +138,6 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *en // newTestLightPeer creates node with light sync mode func newTestLightPeer(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) { - _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcServers, ulcFraction, false, false) + _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcServers, ulcFraction, false, false, true) return c, teardown } diff --git a/light/lightchain.go b/light/lightchain.go index 79eba62c9d..6fc321ae0b 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -112,7 +112,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. if header := bc.GetHeaderByHash(hash); header != nil { log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash) bc.SetHead(header.Number.Uint64() - 1) - log.Error("Chain rewind was successful, resuming normal operation") + log.Info("Chain rewind was successful, resuming normal operation") } } return bc, nil @@ -155,7 +155,11 @@ func (lc *LightChain) loadLastState() error { // Corrupt or empty database, init from scratch lc.Reset() } else { - if header := lc.GetHeaderByHash(head); header != nil { + header := lc.GetHeaderByHash(head) + if header == nil { + // Corrupt or empty database, init from scratch + lc.Reset() + } else { lc.hc.SetCurrentHeader(header) } } @@ -163,7 +167,6 @@ func (lc *LightChain) loadLastState() error { header := lc.hc.CurrentHeader() headerTd := lc.GetTd(header.Hash(), header.Number.Uint64()) log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0))) - return nil } @@ -431,6 +434,17 @@ func (lc *LightChain) GetTdByHash(hash common.Hash) *big.Int { return lc.hc.GetTdByHash(hash) } +// GetHeaderByNumberOdr retrieves the total difficult from the database or +// network by hash and number, caching it (associated with its hash) if found. +func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uint64) *big.Int { + td := lc.GetTd(hash, number) + if td != nil { + return td + } + td, _ = GetTd(ctx, lc.odr, hash, number) + return td +} + // GetHeader retrieves a block header from the database by hash and number, // caching it if found. func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header { diff --git a/light/odr.go b/light/odr.go index 907712ede7..1ea98ca5aa 100644 --- a/light/odr.go +++ b/light/odr.go @@ -82,7 +82,6 @@ func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID { // TrieRequest is the ODR request type for state/storage trie entries type TrieRequest struct { - OdrRequest Id *TrieID Key []byte Proof *NodeSet @@ -95,7 +94,6 @@ func (req *TrieRequest) StoreResult(db ethdb.Database) { // CodeRequest is the ODR request type for retrieving contract code type CodeRequest struct { - OdrRequest Id *TrieID // references storage trie of the account Hash common.Hash Data []byte @@ -108,9 +106,9 @@ func (req *CodeRequest) StoreResult(db ethdb.Database) { // BlockRequest is the ODR request type for retrieving block bodies type BlockRequest struct { - OdrRequest Hash common.Hash Number uint64 + Header *types.Header Rlp []byte } @@ -119,9 +117,8 @@ func (req *BlockRequest) StoreResult(db ethdb.Database) { rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp) } -// ReceiptsRequest is the ODR request type for retrieving block bodies +// ReceiptsRequest is the ODR request type for retrieving receipts. type ReceiptsRequest struct { - OdrRequest Untrusted bool // Indicator whether the result retrieved is trusted or not Hash common.Hash Number uint64 @@ -138,7 +135,6 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { // ChtRequest is the ODR request type for state/storage trie entries type ChtRequest struct { - OdrRequest Untrusted bool // Indicator whether the result retrieved is trusted or not PeerId string // The specified peer id from which to retrieve data. Config *IndexerConfig @@ -193,7 +189,6 @@ type TxStatus struct { // TxStatusRequest is the ODR request type for retrieving transaction status type TxStatusRequest struct { - OdrRequest Hashes []common.Hash Status []TxStatus } diff --git a/light/odr_util.go b/light/odr_util.go index 2c820d40c7..aec0c7b69f 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -19,6 +19,7 @@ package light import ( "bytes" "context" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -30,65 +31,83 @@ import ( var sha3Nil = crypto.Keccak256Hash(nil) +// GetHeaderByNumber retrieves the canonical block header corresponding to the +// given number. func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) { + // Try to find it in the local database first. db := odr.Database() hash := rawdb.ReadCanonicalHash(db, number) - if (hash != common.Hash{}) { - // if there is a canonical hash, there is a header too - header := rawdb.ReadHeader(db, hash, number) - if header == nil { - panic("Canonical hash present but header not found") - } - return header, nil - } - var ( - chtCount, sectionHeadNum uint64 - sectionHead common.Hash - ) - if odr.ChtIndexer() != nil { - chtCount, sectionHeadNum, sectionHead = odr.ChtIndexer().Sections() - canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum) - // if the CHT was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too - for chtCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) { - chtCount-- - if chtCount > 0 { - sectionHeadNum = chtCount*odr.IndexerConfig().ChtSize - 1 - sectionHead = odr.ChtIndexer().SectionHead(chtCount - 1) - canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum) - } + // If there is a canonical hash, there should have a header too. + // But if it's pruned, re-fetch from network again. + if (hash != common.Hash{}) { + if header := rawdb.ReadHeader(db, hash, number); header != nil { + return header, nil } } - if number >= chtCount*odr.IndexerConfig().ChtSize { + // Retrieve the header via ODR, ensure the requested header is covered + // by local trusted CHT. + chts, _, chtHead := odr.ChtIndexer().Sections() + if number >= chts*odr.IndexerConfig().ChtSize { return nil, errNoTrustedCht } - r := &ChtRequest{ChtRoot: GetChtRoot(db, chtCount-1, sectionHead), ChtNum: chtCount - 1, BlockNum: number, Config: odr.IndexerConfig()} + r := &ChtRequest{ + ChtRoot: GetChtRoot(db, chts-1, chtHead), + ChtNum: chts - 1, + BlockNum: number, + Config: odr.IndexerConfig(), + } if err := odr.Retrieve(ctx, r); err != nil { return nil, err } return r.Header, nil } -// GetUntrustedHeaderByNumber fetches specified block header without correctness checking. -// Note this function should only be used in light client checkpoint syncing. +// GetUntrustedHeaderByNumber retrieves specified block header without +// correctness checking. Note this function should only be used in light +// client checkpoint syncing. func GetUntrustedHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64, peerId string) (*types.Header, error) { - r := &ChtRequest{BlockNum: number, ChtNum: number / odr.IndexerConfig().ChtSize, Untrusted: true, PeerId: peerId, Config: odr.IndexerConfig()} + // todo(rjl493456442) it's a hack to retrieve headers which is not covered + // by CHT. Fix it in LES4 + r := &ChtRequest{ + BlockNum: number, + ChtNum: number / odr.IndexerConfig().ChtSize, + Untrusted: true, + PeerId: peerId, + Config: odr.IndexerConfig(), + } if err := odr.Retrieve(ctx, r); err != nil { return nil, err } return r.Header, nil } +// GetCanonicalHash retrieves the canonical block hash corresponding to the number. func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) { hash := rawdb.ReadCanonicalHash(odr.Database(), number) - if (hash != common.Hash{}) { + if hash != (common.Hash{}) { return hash, nil } header, err := GetHeaderByNumber(ctx, odr, number) - if header != nil { - return header.Hash(), nil + if err != nil { + return common.Hash{}, err + } + // number -> canonical mapping already be stored in db, get it. + return header.Hash(), nil +} + +// GetTd retrieves the total difficulty corresponding to the number and hash. +func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*big.Int, error) { + td := rawdb.ReadTd(odr.Database(), hash, number) + if td != nil { + return td, nil + } + _, err := GetHeaderByNumber(ctx, odr, number) + if err != nil { + return nil, err } - return common.Hash{}, err + // -> td mapping already be stored in db, get it. + return rawdb.ReadTd(odr.Database(), hash, number), nil } // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. @@ -96,15 +115,19 @@ func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number ui if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil { return data, nil } - r := &BlockRequest{Hash: hash, Number: number} + // Retrieve the block header first and pass it for verification. + header, err := GetHeaderByNumber(ctx, odr, number) + if err != nil { + return nil, errNoHeader + } + r := &BlockRequest{Hash: hash, Number: number, Header: header} if err := odr.Retrieve(ctx, r); err != nil { return nil, err - } else { - return r.Rlp, nil } + return r.Rlp, nil } -// GetBody retrieves the block body (transactons, uncles) corresponding to the +// GetBody retrieves the block body (transactions, uncles) corresponding to the // hash. func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) { data, err := GetBodyRLP(ctx, odr, hash, number) @@ -122,8 +145,8 @@ func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint6 // back from the stored header and body. func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) { // Retrieve the block header and body contents - header := rawdb.ReadHeader(odr.Database(), hash, number) - if header == nil { + header, err := GetHeaderByNumber(ctx, odr, number) + if err != nil { return nil, errNoHeader } body, err := GetBody(ctx, odr, hash, number) @@ -140,7 +163,11 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num // Assume receipts are already stored locally and attempt to retrieve. receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number) if receipts == nil { - r := &ReceiptsRequest{Hash: hash, Number: number} + header, err := GetHeaderByNumber(ctx, odr, number) + if err != nil { + return nil, errNoHeader + } + r := &ReceiptsRequest{Hash: hash, Number: number, Header: header} if err := odr.Retrieve(ctx, r); err != nil { return nil, err } @@ -171,7 +198,6 @@ func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number if err != nil { return nil, err } - // Return the logs without deriving any computed fields on the receipts logs := make([][]*types.Log, len(receipts)) for i, receipt := range receipts { logs[i] = receipt.Logs @@ -203,64 +229,51 @@ func GetUntrustedBlockLogs(ctx context.Context, odr OdrBackend, header *types.He return logs, nil } -// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes -func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxList []uint64) ([][]byte, error) { - var ( - db = odr.Database() - result = make([][]byte, len(sectionIdxList)) - reqList []uint64 - reqIdx []int - ) - +// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to +// the given bit index and section indexes. +func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) { var ( - bloomTrieCount, sectionHeadNum uint64 - sectionHead common.Hash + reqIndex []int + reqSections []uint64 + db = odr.Database() + result = make([][]byte, len(sections)) ) - if odr.BloomTrieIndexer() != nil { - bloomTrieCount, sectionHeadNum, sectionHead = odr.BloomTrieIndexer().Sections() - canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum) - // if the BloomTrie was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too - for bloomTrieCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) { - bloomTrieCount-- - if bloomTrieCount > 0 { - sectionHeadNum = bloomTrieCount*odr.IndexerConfig().BloomTrieSize - 1 - sectionHead = odr.BloomTrieIndexer().SectionHead(bloomTrieCount - 1) - canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum) - } - } - } - - for i, sectionIdx := range sectionIdxList { - sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*odr.IndexerConfig().BloomSize-1) - // if we don't have the canonical hash stored for this section head number, we'll still look for - // an entry with a zero sectionHead (we store it with zero section head too if we don't know it - // at the time of the retrieval) - bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead) - if err == nil { + blooms, _, sectionHead := odr.BloomTrieIndexer().Sections() + for i, section := range sections { + sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1) + // If we don't have the canonical hash stored for this section head number, + // we'll still look for an entry with a zero sectionHead (we store it with + // zero section head too if we don't know it at the time of the retrieval) + if bloomBits, _ := rawdb.ReadBloomBits(db, bit, section, sectionHead); len(bloomBits) != 0 { result[i] = bloomBits - } else { - // TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index - if sectionIdx >= bloomTrieCount { - return nil, errNoTrustedBloomTrie - } - reqList = append(reqList, sectionIdx) - reqIdx = append(reqIdx, i) + continue } + // TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index + if section >= blooms { + return nil, errNoTrustedBloomTrie + } + reqSections = append(reqSections, section) + reqIndex = append(reqIndex, i) } - if reqList == nil { + // Find all bloombits in database, nothing to query via odr, return. + if reqSections == nil { return result, nil } - - r := &BloomRequest{BloomTrieRoot: GetBloomTrieRoot(db, bloomTrieCount-1, sectionHead), BloomTrieNum: bloomTrieCount - 1, - BitIdx: bitIdx, SectionIndexList: reqList, Config: odr.IndexerConfig()} + // Send odr request to retrieve missing bloombits. + r := &BloomRequest{ + BloomTrieRoot: GetBloomTrieRoot(db, blooms-1, sectionHead), + BloomTrieNum: blooms - 1, + BitIdx: bit, + SectionIndexList: reqSections, + Config: odr.IndexerConfig(), + } if err := odr.Retrieve(ctx, r); err != nil { return nil, err - } else { - for i, idx := range reqIdx { - result[idx] = r.BloomBits[i] - } - return result, nil } + for i, idx := range reqIndex { + result[idx] = r.BloomBits[i] + } + return result, nil } // GetTransaction retrieves a canonical transaction by hash and also returns its position in the chain @@ -268,17 +281,16 @@ func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*t r := &TxStatusRequest{Hashes: []common.Hash{txHash}} if err := odr.Retrieve(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded { return nil, common.Hash{}, 0, 0, err - } else { - pos := r.Status[0].Lookup - // first ensure that we have the header, otherwise block body retrieval will fail - // also verify if this is a canonical block by getting the header by number and checking its hash - if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash { - return nil, common.Hash{}, 0, 0, err - } - if body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex); err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash { - return nil, common.Hash{}, 0, 0, err - } else { - return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil - } } + pos := r.Status[0].Lookup + // first ensure that we have the header, otherwise block body retrieval will fail + // also verify if this is a canonical block by getting the header by number and checking its hash + if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash { + return nil, common.Hash{}, 0, 0, err + } + body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex) + if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash { + return nil, common.Hash{}, 0, 0, err + } + return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil } diff --git a/light/postprocess.go b/light/postprocess.go index af3b257923..8efc8d6566 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -17,6 +17,7 @@ package light import ( + "bytes" "context" "encoding/binary" "errors" @@ -24,6 +25,7 @@ import ( "math/big" "time" + "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/core" @@ -128,23 +130,27 @@ func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common // ChtIndexerBackend implements core.ChainIndexerBackend. type ChtIndexerBackend struct { + disablePruning bool diskdb, trieTable ethdb.Database odr OdrBackend triedb *trie.Database + trieset mapset.Set section, sectionSize uint64 lastHash common.Hash trie *trie.Trie } // NewChtIndexer creates a Cht chain indexer -func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64) *core.ChainIndexer { +func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer { trieTable := rawdb.NewTable(db, ChtTablePrefix) backend := &ChtIndexerBackend{ - diskdb: db, - odr: odr, - trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down - sectionSize: size, + diskdb: db, + odr: odr, + trieTable: trieTable, + triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down + trieset: mapset.NewSet(), + sectionSize: size, + disablePruning: disablePruning, } return core.NewChainIndexer(db, rawdb.NewTable(db, "chtIndexV2-"), backend, size, confirms, time.Millisecond*100, "cht") } @@ -189,7 +195,6 @@ func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSecti c.trie, err = trie.New(root, c.triedb) } } - c.section = section return err } @@ -216,13 +221,83 @@ func (c *ChtIndexerBackend) Commit() error { if err != nil { return err } - c.triedb.Commit(root, false) - + // Pruning historical trie nodes if necessary. + if !c.disablePruning { + // Flush the triedb and track the latest trie nodes. + c.trieset.Clear() + c.triedb.Commit(root, false, func(hash common.Hash) { c.trieset.Add(hash) }) + + it := c.trieTable.NewIterator(nil, nil) + defer it.Release() + + var ( + deleted int + remaining int + t = time.Now() + ) + for it.Next() { + trimmed := bytes.TrimPrefix(it.Key(), []byte(ChtTablePrefix)) + if !c.trieset.Contains(common.BytesToHash(trimmed)) { + c.trieTable.Delete(trimmed) + deleted += 1 + } else { + remaining += 1 + } + } + log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", remaining, "elapsed", common.PrettyDuration(time.Since(t))) + } else { + c.triedb.Commit(root, false, nil) + } log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) StoreChtRoot(c.diskdb, c.section, c.lastHash, root) return nil } +// PruneSections implements core.ChainIndexerBackend which deletes all +// chain data(except hash<->number mappings) older than the specified +// threshold. +func (c *ChtIndexerBackend) Prune(threshold uint64) error { + // Short circuit if the light pruning is disabled. + if c.disablePruning { + return nil + } + t := time.Now() + // Always keep genesis header in database. + start, end := uint64(1), (threshold+1)*c.sectionSize + + var batch = c.diskdb.NewBatch() + for { + numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240) + if len(numbers) == 0 { + break + } + for i := 0; i < len(numbers); i++ { + // Keep hash<->number mapping in database otherwise the hash based + // API(e.g. GetReceipt, GetLogs) will be broken. + // + // Storage size wise, the size of a mapping is ~41bytes. For one + // section is about 1.3MB which is acceptable. + // + // In order to totally get rid of this index, we need an additional + // flag to specify how many historical data light client can serve. + rawdb.DeleteCanonicalHash(batch, numbers[i]) + rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i]) + } + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + } + start = numbers[len(numbers)-1] + 1 + } + if err := batch.Write(); err != nil { + return err + } + log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t))) + return nil +} + var ( bloomTriePrefix = []byte("bltRoot-") // bloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash BloomTrieTablePrefix = "blt-" @@ -245,8 +320,10 @@ func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root // BloomTrieIndexerBackend implements core.ChainIndexerBackend type BloomTrieIndexerBackend struct { + disablePruning bool diskdb, trieTable ethdb.Database triedb *trie.Database + trieset mapset.Set odr OdrBackend section uint64 parentSize uint64 @@ -257,15 +334,17 @@ type BloomTrieIndexerBackend struct { } // NewBloomTrieIndexer creates a BloomTrie chain indexer -func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64) *core.ChainIndexer { +func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer { trieTable := rawdb.NewTable(db, BloomTrieTablePrefix) backend := &BloomTrieIndexerBackend{ - diskdb: db, - odr: odr, - trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down - parentSize: parentSize, - size: size, + diskdb: db, + odr: odr, + trieTable: trieTable, + triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down + trieset: mapset.NewSet(), + parentSize: parentSize, + size: size, + disablePruning: disablePruning, } backend.bloomTrieRatio = size / parentSize backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio) @@ -303,7 +382,6 @@ func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section } }() } - for i := uint(0); i < types.BloomBitLength; i++ { indexCh <- i } @@ -380,10 +458,51 @@ func (b *BloomTrieIndexerBackend) Commit() error { if err != nil { return err } - b.triedb.Commit(root, false) - + // Pruning historical trie nodes if necessary. + if !b.disablePruning { + // Flush the triedb and track the latest trie nodes. + b.trieset.Clear() + b.triedb.Commit(root, false, func(hash common.Hash) { b.trieset.Add(hash) }) + + it := b.trieTable.NewIterator(nil, nil) + defer it.Release() + + var ( + deleted int + remaining int + t = time.Now() + ) + for it.Next() { + trimmed := bytes.TrimPrefix(it.Key(), []byte(BloomTrieTablePrefix)) + if !b.trieset.Contains(common.BytesToHash(trimmed)) { + b.trieTable.Delete(trimmed) + deleted += 1 + } else { + remaining += 1 + } + } + log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", remaining, "elapsed", common.PrettyDuration(time.Since(t))) + } else { + b.triedb.Commit(root, false, nil) + } sectionHead := b.sectionHeads[b.bloomTrieRatio-1] - log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root) + log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) + + return nil +} + +// Prune implements core.ChainIndexerBackend which deletes all +// bloombits which older than the specified threshold. +func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error { + // Short circuit if the light pruning is disabled. + if b.disablePruning { + return nil + } + start := time.Now() + for i := uint(0); i < types.BloomBitLength; i++ { + rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio) + } + log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start))) return nil } diff --git a/params/network_params.go b/params/network_params.go index ab2e845a45..9311b5e2d5 100644 --- a/params/network_params.go +++ b/params/network_params.go @@ -53,9 +53,15 @@ const ( // CheckpointProcessConfirmations is the number before a checkpoint is generated CheckpointProcessConfirmations = 256 - // ImmutabilityThreshold is the number of blocks after which a chain segment is + // FullImmutabilityThreshold is the number of blocks after which a chain segment is // considered immutable (i.e. soft finality). It is used by the downloader as a // hard limit against deep ancestors, by the blockchain against deep reorgs, by // the freezer as the cutoff threshold and by clique as the snapshot trust limit. - ImmutabilityThreshold = 90000 + FullImmutabilityThreshold = 90000 + + // LightImmutabilityThreshold is the number of blocks after which a header chain + // segment is considered immutable for light client(i.e. soft finality). It is used by + // the downloader as a hard limit against deep ancestors, by the blockchain against deep + // reorgs, by the light pruner as the pruning validity guarantee. + LightImmutabilityThreshold = 30000 ) diff --git a/trie/database.go b/trie/database.go index 00d4baddf6..4f310a776c 100644 --- a/trie/database.go +++ b/trie/database.go @@ -693,7 +693,7 @@ func (db *Database) Cap(limit common.StorageSize) error { // // Note, this method is a non-synchronized mutator. It is unsafe to call this // concurrently with other mutators. -func (db *Database) Commit(node common.Hash, report bool) error { +func (db *Database) Commit(node common.Hash, report bool, callback func(common.Hash)) error { // Create a database batch to flush persistent data out. It is important that // outside code doesn't see an inconsistent state (referenced data removed from // memory cache during commit but not yet in persistent storage). This is ensured @@ -732,7 +732,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { nodes, storage := len(db.dirties), db.dirtiesSize uncacher := &cleaner{db} - if err := db.commit(node, batch, uncacher); err != nil { + if err := db.commit(node, batch, uncacher, callback); err != nil { log.Error("Failed to commit trie from trie database", "err", err) return err } @@ -771,7 +771,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { } // commit is the private locked version of Commit. -func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error { +func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner, callback func(common.Hash)) error { // If the node does not exist, it's a previously committed node node, ok := db.dirties[hash] if !ok { @@ -780,7 +780,7 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane var err error node.forChilds(func(child common.Hash) { if err == nil { - err = db.commit(child, batch, uncacher) + err = db.commit(child, batch, uncacher, callback) } }) if err != nil { @@ -789,6 +789,9 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane if err := batch.Put(hash[:], node.rlp()); err != nil { return err } + if callback != nil { + callback(hash) + } // If we've reached an optimal batch size, commit and start over if batch.ValueSize() >= ethdb.IdealBatchSize { if err := batch.Write(); err != nil { diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 54dd3069f9..75a0a99e51 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -301,7 +301,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { } tr.Commit(nil) if !memonly { - triedb.Commit(tr.Hash(), true) + triedb.Commit(tr.Hash(), true, nil) } wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil) @@ -392,7 +392,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { } root, _ := ctr.Commit(nil) if !memonly { - triedb.Commit(root, true) + triedb.Commit(root, true, nil) } barNodeHash := common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e") var ( diff --git a/trie/trie_test.go b/trie/trie_test.go index 172572dddc..588562146a 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -88,7 +88,7 @@ func testMissingNode(t *testing.T, memonly bool) { updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") root, _ := trie.Commit(nil) if !memonly { - triedb.Commit(root, true) + triedb.Commit(root, true, nil) } trie, _ = New(root, triedb) From af258efdb995914394b02e75b7caa952887054a5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 13 Jul 2020 11:17:49 +0200 Subject: [PATCH 03/23] light: goimports -w (#21325) --- light/postprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/light/postprocess.go b/light/postprocess.go index 8efc8d6566..996e989f96 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -25,7 +25,7 @@ import ( "math/big" "time" - "github.com/deckarep/golang-set" + mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/core" From 2e08dad9e6b3b0f675d144cd41cbbef43439a8b7 Mon Sep 17 00:00:00 2001 From: libotony Date: Mon, 13 Jul 2020 17:20:47 +0800 Subject: [PATCH 04/23] p2p/discv5: unset pingEcho on pong timeout (#21324) --- p2p/discv5/net.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index c912cba7d1..53e00a3881 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -1037,6 +1037,9 @@ func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error { net.db.ensureExpirer() } } + if ev == pongTimeout { + n.pingEcho = nil // clean up if pongtimeout + } if n.state == nil { n.state = unknown //??? } From 6cf6e1d7536adf5b8d3f130720848af9d820b358 Mon Sep 17 00:00:00 2001 From: Tien Date: Mon, 13 Jul 2020 16:22:30 +0700 Subject: [PATCH 05/23] README.md: point Go API reference link to pkg.go.dev (#21321) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 557604d651..ddb885dfdc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Official Golang implementation of the Ethereum protocol. [![API Reference]( https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667 -)](https://godoc.org/github.com/ethereum/go-ethereum) +)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) [![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) [![Travis](https://travis-ci.org/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.org/ethereum/go-ethereum) [![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv) @@ -108,7 +108,7 @@ accounts available between them.* ### Full node on the Rinkeby test network -Go Ethereum also supports connecting to the older proof-of-authority based test network +Go Ethereum also supports connecting to the older proof-of-authority based test network called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community. ```shell @@ -117,10 +117,10 @@ $ geth --rinkeby console ### Full node on the Ropsten test network -In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The +In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such, it has certain extra overhead and is more susceptible to reorganization attacks due to the -network's low difficulty/security. +network's low difficulty/security. ```shell $ geth --ropsten console From 4edbc1f2bb7892f8e77d7eb2a676e0fc7c029be0 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 13 Jul 2020 18:45:39 +0800 Subject: [PATCH 06/23] internal/ethapi: cap txfee for SignTransaction and Resend (#21231) --- internal/ethapi/api.go | 44 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bd4ea6fdd4..ac0b7bed38 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -406,6 +406,10 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") } + // Before actually sign the transaction, ensure the transaction fee is reasonable. + if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { + return nil, err + } signed, err := s.signTransaction(ctx, &args, passwd) if err != nil { log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) @@ -1545,10 +1549,8 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { // If the transaction fee cap is already specified, ensure the // fee of the given transaction is _reasonable_. - feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))), new(big.Float).SetInt(big.NewInt(params.Ether))) - feeFloat, _ := feeEth.Float64() - if b.RPCTxFeeCap() != 0 && feeFloat > b.RPCTxFeeCap() { - return common.Hash{}, fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, b.RPCTxFeeCap()) + if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil { + return common.Hash{}, err } if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err @@ -1672,6 +1674,10 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } + // Before actually sign the transaction, ensure the transaction fee is reasonable. + if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { + return nil, err + } tx, err := s.sign(args.From, args.toTransaction()) if err != nil { return nil, err @@ -1720,11 +1726,24 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr return common.Hash{}, err } matchTx := sendArgs.toTransaction() + + // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. + var price = matchTx.GasPrice() + if gasPrice != nil { + price = gasPrice.ToInt() + } + var gas = matchTx.Gas() + if gasLimit != nil { + gas = uint64(*gasLimit) + } + if err := checkTxFee(price, gas, s.b.RPCTxFeeCap()); err != nil { + return common.Hash{}, err + } + // Iterate the pending list for replacement pending, err := s.b.GetPoolTransactions() if err != nil { return common.Hash{}, err } - for _, p := range pending { var signer types.Signer = types.HomesteadSigner{} if p.Protected() { @@ -1901,3 +1920,18 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { func (s *PublicNetAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } + +// checkTxFee is an internal function used to check whether the fee of +// the given transaction is _reasonable_(under the cap). +func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { + // Short circuit if there is no cap for transaction fee at all. + if cap == 0 { + return nil + } + feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))), new(big.Float).SetInt(big.NewInt(params.Ether))) + feeFloat, _ := feeEth.Float64() + if feeFloat > cap { + return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap) + } + return nil +} From 7d5267e3a2acf273215105f04a69870e79d80d77 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 13 Jul 2020 16:20:20 +0200 Subject: [PATCH 07/23] .github: Change Code Owners (#21326) * modify code owners * add marius --- .github/CODEOWNERS | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ac7915f22..59e73396a6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,21 +3,21 @@ accounts/usbwallet @karalabe accounts/scwallet @gballet -accounts/abi @gballet +accounts/abi @gballet @MariusVanDerWijden cmd/clef @holiman cmd/puppeth @karalabe consensus @karalabe core/ @karalabe @holiman @rjl493456442 -dashboard/ @kurkomisi eth/ @karalabe @holiman @rjl493456442 graphql/ @gballet les/ @zsfelfoldi @rjl493456442 light/ @zsfelfoldi @rjl493456442 mobile/ @karalabe @ligi +node/ @fjl @renaynay p2p/ @fjl @zsfelfoldi rpc/ @fjl @holiman -p2p/simulations @zelig @janos @justelad -p2p/protocols @zelig @janos @justelad -p2p/testing @zelig @janos @justelad +p2p/simulations @fjl +p2p/protocols @fjl +p2p/testing @fjl signer/ @holiman -whisper/ @gballet @gluk256 +whisper/ @gballet From 79addac698d4f066b29bb90140885c815be55a07 Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 14 Jul 2020 02:43:30 +0800 Subject: [PATCH 08/23] core/rawdb: better log messages for ancient failure (#21327) --- core/rawdb/database.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 583573407e..eb3f86a76e 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -137,7 +137,10 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // If the freezer already contains something, ensure that the genesis blocks // match, otherwise we might mix up freezers across chains and destroy both // the freezer and the key-value store. - if frgenesis, _ := frdb.Ancient(freezerHashTable, 0); !bytes.Equal(kvgenesis, frgenesis) { + frgenesis, err := frdb.Ancient(freezerHashTable, 0) + if err != nil { + return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) + } else if !bytes.Equal(kvgenesis, frgenesis) { return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) } // Key-value store and freezer belong to the same network. Ensure that they From 6ef4495a8fbda46c888b8f746fa006b0dfcdcc4f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 13 Jul 2020 22:25:45 +0200 Subject: [PATCH 09/23] p2p/discover: require table nodes to have an IP (#21330) This fixes a corner case in discv5. The issue cannot happen in discv4 because it performs IP checks on all incoming node information. --- p2p/discover/table.go | 3 +++ p2p/discover/table_test.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 6d48ab00cd..010fa47f52 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -520,6 +520,9 @@ func (tab *Table) delete(node *node) { } func (tab *Table) addIP(b *bucket, ip net.IP) bool { + if len(ip) == 0 { + return false // Nodes without IP cannot be added. + } if netutil.IsLAN(ip) { return true } diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 895c284b27..562691e5b9 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -58,7 +58,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding // Fill up the sender's bucket. pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") - pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{}, 99, 99)) + pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99)) last := fillBucket(tab, pingSender) // Add the sender as if it just pinged us. Revalidate should replace the last node in From 5b081ab214d8d6373cbfdd9464f2503934b9bb68 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:35:32 +0200 Subject: [PATCH 10/23] cmd/clef: change --rpcport to --http.port and update flags in docs (#21318) --- cmd/abigen/main.go | 5 +- cmd/checkpoint-admin/main.go | 6 +- cmd/clef/README.md | 10 +-- cmd/clef/main.go | 94 +++++++++++++++++++++- cmd/ethkey/main.go | 6 +- cmd/evm/main.go | 5 +- cmd/geth/main.go | 3 +- cmd/geth/usage.go | 91 +++------------------ cmd/utils/flags.go | 38 +-------- internal/flags/helpers.go | 152 +++++++++++++++++++++++++++++++++++ 10 files changed, 274 insertions(+), 136 deletions(-) create mode 100644 internal/flags/helpers.go diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index ed4a3b8870..a74b0396d4 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1" ) @@ -100,7 +101,7 @@ var ( ) func init() { - app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") + app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") app.Flags = []cli.Flag{ abiFlag, binFlag, @@ -117,7 +118,7 @@ func init() { aliasFlag, } app.Action = utils.MigrateFlags(abigen) - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } func abigen(c *cli.Context) error { diff --git a/cmd/checkpoint-admin/main.go b/cmd/checkpoint-admin/main.go index b4d8e0db5a..0fb5532147 100644 --- a/cmd/checkpoint-admin/main.go +++ b/cmd/checkpoint-admin/main.go @@ -22,8 +22,8 @@ import ( "fmt" "os" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common/fdlimit" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1" ) @@ -37,7 +37,7 @@ var ( var app *cli.App func init() { - app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") + app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") app.Commands = []cli.Command{ commandStatus, commandDeploy, @@ -48,7 +48,7 @@ func init() { oracleFlag, nodeURLFlag, } - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } // Commonly used command line flags. diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 93f78ac2dc..700f0a144b 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -33,12 +33,12 @@ GLOBAL OPTIONS: --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength --nousb Disables monitoring for and managing USB hardware wallets --pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm") - --rpcaddr value HTTP-RPC server listening interface (default: "localhost") - --rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") + --http.addr value HTTP-RPC server listening interface (default: "localhost") + --http.vhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") --ipcdisable Disable the IPC-RPC server --ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it) - --rpc Enable the HTTP-RPC server - --rpcport value HTTP-RPC server listening port (default: 8550) + --http Enable the HTTP-RPC server + --http.port value HTTP-RPC server listening port (default: 8550) --signersecret value A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash --4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json") --auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log") @@ -113,7 +113,7 @@ Some snags and todos ### External API -Clef listens to HTTP requests on `rpcaddr`:`rpcport` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification). +Clef listens to HTTP requests on `http.addr`:`http.port` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification). Some of these calls can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a user decides to ignore the confirmation request. diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 10cb4628b8..8c8778c249 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -32,6 +32,7 @@ import ( "os/user" "path/filepath" "runtime" + "sort" "strings" "time" @@ -43,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -52,6 +54,7 @@ import ( "github.com/ethereum/go-ethereum/signer/fourbyte" "github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/storage" + colorable "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "gopkg.in/urfave/cli.v1" @@ -101,10 +104,15 @@ var ( Usage: "Chain id to use for signing (1=mainnet, 3=Ropsten, 4=Rinkeby, 5=Goerli)", } rpcPortFlag = cli.IntFlag{ - Name: "rpcport", + Name: "http.port", Usage: "HTTP-RPC server listening port", Value: node.DefaultHTTPPort + 5, } + legacyRPCPortFlag = cli.IntFlag{ + Name: "rpcport", + Usage: "HTTP-RPC server listening port (Deprecated, please use --http.port).", + Value: node.DefaultHTTPPort + 5, + } signerSecretFlag = cli.StringFlag{ Name: "signersecret", Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", @@ -215,6 +223,42 @@ The gendoc generates example structures of the json-rpc communication types. `} ) +// AppHelpFlagGroups is the application flags, grouped by functionality. +var AppHelpFlagGroups = []flags.FlagGroup{ + { + Name: "FLAGS", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + configdirFlag, + chainIdFlag, + utils.LightKDFFlag, + utils.NoUSBFlag, + utils.SmartCardDaemonPathFlag, + utils.HTTPListenAddrFlag, + utils.HTTPVirtualHostsFlag, + utils.IPCDisabledFlag, + utils.IPCPathFlag, + utils.HTTPEnabledFlag, + rpcPortFlag, + signerSecretFlag, + customDBFlag, + auditLogFlag, + ruleFlag, + stdiouiFlag, + testFlag, + advancedMode, + acceptFlag, + }, + }, + { + Name: "ALIASED (deprecated)", + Flags: []cli.Flag{ + legacyRPCPortFlag, + }, + }, +} + func init() { app.Name = "Clef" app.Usage = "Manage Ethereum account operations" @@ -240,6 +284,7 @@ func init() { testFlag, advancedMode, acceptFlag, + legacyRPCPortFlag, } app.Action = signer app.Commands = []cli.Command{initCommand, @@ -248,7 +293,41 @@ func init() { delCredentialCommand, newAccountCommand, gendocCommand} - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.CommandHelpTemplate + // Override the default app help template + cli.AppHelpTemplate = flags.ClefAppHelpTemplate + + // Override the default app help printer, but only for the global app help + originalHelpPrinter := cli.HelpPrinter + cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) { + if tmpl == flags.ClefAppHelpTemplate { + // Render out custom usage screen + originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups}) + } else if tmpl == flags.CommandHelpTemplate { + // Iterate over all command specific flags and categorize them + categorized := make(map[string][]cli.Flag) + for _, flag := range data.(cli.Command).Flags { + if _, ok := categorized[flag.String()]; !ok { + categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag) + } + } + + // sort to get a stable ordering + sorted := make([]flags.FlagGroup, 0, len(categorized)) + for cat, flgs := range categorized { + sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs}) + } + sort.Sort(flags.ByCategory(sorted)) + + // add sorted array to data and render with default printer + originalHelpPrinter(w, tmpl, map[string]interface{}{ + "cmd": data, + "categorizedFlags": sorted, + }) + } else { + originalHelpPrinter(w, tmpl, data) + } + } } func main() { @@ -597,8 +676,17 @@ func signer(c *cli.Context) error { } handler := node.NewHTTPHandlerStack(srv, cors, vhosts) + // set port + port := c.Int(rpcPortFlag.Name) + if c.GlobalIsSet(legacyRPCPortFlag.Name) { + if !c.GlobalIsSet(rpcPortFlag.Name) { + port = c.Int(legacyRPCPortFlag.Name) + } + log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port") + } + // start http server - httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) + httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port) httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) diff --git a/cmd/ethkey/main.go b/cmd/ethkey/main.go index dbc4960588..6db39174c4 100644 --- a/cmd/ethkey/main.go +++ b/cmd/ethkey/main.go @@ -20,7 +20,7 @@ import ( "fmt" "os" - "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/flags" "gopkg.in/urfave/cli.v1" ) @@ -35,7 +35,7 @@ var gitDate = "" var app *cli.App func init() { - app = utils.NewApp(gitCommit, gitDate, "an Ethereum key manager") + app = flags.NewApp(gitCommit, gitDate, "an Ethereum key manager") app.Commands = []cli.Command{ commandGenerate, commandInspect, @@ -43,7 +43,7 @@ func init() { commandSignMessage, commandVerifyMessage, } - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } // Commonly used command line flags. diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 473020d5f1..2d5e9763df 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/flags" "gopkg.in/urfave/cli.v1" ) @@ -31,7 +32,7 @@ var gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags) var gitDate = "" var ( - app = utils.NewApp(gitCommit, gitDate, "the evm command line interface") + app = flags.NewApp(gitCommit, gitDate, "the evm command line interface") DebugFlag = cli.BoolFlag{ Name: "debug", @@ -180,7 +181,7 @@ func init() { stateTestCommand, stateTransitionCommand, } - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } func main() { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e82e0ec540..1592f774a6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -53,7 +54,7 @@ var ( gitCommit = "" gitDate = "" // The app that holds all commands and flags. - app = utils.NewApp(gitCommit, gitDate, "the go-ethereum command line interface") + app = flags.NewApp(gitCommit, gitDate, "the go-ethereum command line interface") // flags that configure the node nodeFlags = []cli.Flag{ utils.IdentityFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 05bd281308..b80f05edb8 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -24,44 +24,12 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" cli "gopkg.in/urfave/cli.v1" ) -// AppHelpTemplate is the test template for the default, global app help topic. -var AppHelpTemplate = `NAME: - {{.App.Name}} - {{.App.Usage}} - - Copyright 2013-2019 The go-ethereum Authors - -USAGE: - {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if .App.Version}} -VERSION: - {{.App.Version}} - {{end}}{{if len .App.Authors}} -AUTHOR(S): - {{range .App.Authors}}{{ . }}{{end}} - {{end}}{{if .App.Commands}} -COMMANDS: - {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .FlagGroups}} -{{range .FlagGroups}}{{.Name}} OPTIONS: - {{range .Flags}}{{.}} - {{end}} -{{end}}{{end}}{{if .App.Copyright }} -COPYRIGHT: - {{.App.Copyright}} - {{end}} -` - -// flagGroup is a collection of flags belonging to a single topic. -type flagGroup struct { - Name string - Flags []cli.Flag -} - // AppHelpFlagGroups is the application flags, grouped by functionality. -var AppHelpFlagGroups = []flagGroup{ +var AppHelpFlagGroups = []flags.FlagGroup{ { Name: "ETHEREUM", Flags: []cli.Flag{ @@ -272,53 +240,14 @@ var AppHelpFlagGroups = []flagGroup{ }, } -// byCategory sorts an array of flagGroup by Name in the order -// defined in AppHelpFlagGroups. -type byCategory []flagGroup - -func (a byCategory) Len() int { return len(a) } -func (a byCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byCategory) Less(i, j int) bool { - iCat, jCat := a[i].Name, a[j].Name - iIdx, jIdx := len(AppHelpFlagGroups), len(AppHelpFlagGroups) // ensure non categorized flags come last - - for i, group := range AppHelpFlagGroups { - if iCat == group.Name { - iIdx = i - } - if jCat == group.Name { - jIdx = i - } - } - - return iIdx < jIdx -} - -func flagCategory(flag cli.Flag) string { - for _, category := range AppHelpFlagGroups { - for _, flg := range category.Flags { - if flg.GetName() == flag.GetName() { - return category.Name - } - } - } - return "MISC" -} - func init() { // Override the default app help template - cli.AppHelpTemplate = AppHelpTemplate - - // Define a one shot struct to pass to the usage template - type helpData struct { - App interface{} - FlagGroups []flagGroup - } + cli.AppHelpTemplate = flags.AppHelpTemplate // Override the default app help printer, but only for the global app help originalHelpPrinter := cli.HelpPrinter cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) { - if tmpl == AppHelpTemplate { + if tmpl == flags.AppHelpTemplate { // Iterate over all the flags and add any uncategorized ones categorized := make(map[string]struct{}) for _, group := range AppHelpFlagGroups { @@ -350,22 +279,22 @@ func init() { }() } // Render out custom usage screen - originalHelpPrinter(w, tmpl, helpData{data, AppHelpFlagGroups}) - } else if tmpl == utils.CommandHelpTemplate { + originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups}) + } else if tmpl == flags.CommandHelpTemplate { // Iterate over all command specific flags and categorize them categorized := make(map[string][]cli.Flag) for _, flag := range data.(cli.Command).Flags { if _, ok := categorized[flag.String()]; !ok { - categorized[flagCategory(flag)] = append(categorized[flagCategory(flag)], flag) + categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag) } } // sort to get a stable ordering - sorted := make([]flagGroup, 0, len(categorized)) + sorted := make([]flags.FlagGroup, 0, len(categorized)) for cat, flgs := range categorized { - sorted = append(sorted, flagGroup{cat, flgs}) + sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs}) } - sort.Sort(byCategory(sorted)) + sort.Sort(flags.ByCategory(sorted)) // add sorted array to data and render with default printer originalHelpPrinter(w, tmpl, map[string]interface{}{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c5e2aa8d65..30e4f783bf 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -48,6 +48,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -67,30 +68,6 @@ import ( cli "gopkg.in/urfave/cli.v1" ) -var ( - CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} [arguments...] -{{if .cmd.Description}}{{.cmd.Description}} -{{end}}{{if .cmd.Subcommands}} -SUBCOMMANDS: - {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .categorizedFlags}} -{{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS: -{{range $categorized.Flags}}{{"\t"}}{{.}} -{{end}} -{{end}}{{end}}` - - OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] -{{if .Description}}{{.Description}} -{{end}}{{if .Subcommands}} -SUBCOMMANDS: - {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .Flags}} -OPTIONS: -{{range $.Flags}} {{.}} -{{end}} -{{end}}` -) - func init() { cli.AppHelpTemplate = `{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] @@ -104,21 +81,10 @@ GLOBAL OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}} ` - cli.CommandHelpTemplate = CommandHelpTemplate + cli.CommandHelpTemplate = flags.CommandHelpTemplate cli.HelpPrinter = printHelp } -// NewApp creates an app with sane defaults. -func NewApp(gitCommit, gitDate, usage string) *cli.App { - app := cli.NewApp() - app.Name = filepath.Base(os.Args[0]) - app.Author = "" - app.Email = "" - app.Version = params.VersionWithCommit(gitCommit, gitDate) - app.Usage = usage - return app -} - func printHelp(out io.Writer, templ string, data interface{}) { funcMap := template.FuncMap{"join": strings.Join} t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go new file mode 100644 index 0000000000..900fec454c --- /dev/null +++ b/internal/flags/helpers.go @@ -0,0 +1,152 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package flags + +import ( + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/params" + cli "gopkg.in/urfave/cli.v1" +) + +var ( + CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} [arguments...] +{{if .cmd.Description}}{{.cmd.Description}} +{{end}}{{if .cmd.Subcommands}} +SUBCOMMANDS: + {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .categorizedFlags}} +{{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS: +{{range $categorized.Flags}}{{"\t"}}{{.}} +{{end}} +{{end}}{{end}}` + + OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] +{{if .Description}}{{.Description}} +{{end}}{{if .Subcommands}} +SUBCOMMANDS: + {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .Flags}} +OPTIONS: +{{range $.Flags}} {{.}} +{{end}} +{{end}}` + + // AppHelpTemplate is the test template for the default, global app help topic. + AppHelpTemplate = `NAME: + {{.App.Name}} - {{.App.Usage}} + + Copyright 2013-2019 The go-ethereum Authors + +USAGE: + {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .App.Version}} +VERSION: + {{.App.Version}} + {{end}}{{if len .App.Authors}} +AUTHOR(S): + {{range .App.Authors}}{{ . }}{{end}} + {{end}}{{if .App.Commands}} +COMMANDS: + {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .FlagGroups}} +{{range .FlagGroups}}{{.Name}} OPTIONS: + {{range .Flags}}{{.}} + {{end}} +{{end}}{{end}}{{if .App.Copyright }} +COPYRIGHT: + {{.App.Copyright}} + {{end}} +` + // ClefAppHelpTemplate is the template for the default, global app help topic. + ClefAppHelpTemplate = `NAME: + {{.App.Name}} - {{.App.Usage}} + + Copyright 2013-2019 The go-ethereum Authors + +USAGE: + {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .App.Version}} +COMMANDS: + {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .FlagGroups}} +{{range .FlagGroups}}{{.Name}} OPTIONS: + {{range .Flags}}{{.}} + {{end}} +{{end}}{{end}}{{if .App.Copyright }} +COPYRIGHT: + {{.App.Copyright}} + {{end}} +` +) + +// HelpData is a one shot struct to pass to the usage template +type HelpData struct { + App interface{} + FlagGroups []FlagGroup +} + +// FlagGroup is a collection of flags belonging to a single topic. +type FlagGroup struct { + Name string + Flags []cli.Flag +} + +// byCategory sorts an array of FlagGroup by Name in the order +// defined in AppHelpFlagGroups. +type ByCategory []FlagGroup + +func (a ByCategory) Len() int { return len(a) } +func (a ByCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByCategory) Less(i, j int) bool { + iCat, jCat := a[i].Name, a[j].Name + iIdx, jIdx := len(a), len(a) // ensure non categorized flags come last + + for i, group := range a { + if iCat == group.Name { + iIdx = i + } + if jCat == group.Name { + jIdx = i + } + } + + return iIdx < jIdx +} + +func FlagCategory(flag cli.Flag, flagGroups []FlagGroup) string { + for _, category := range flagGroups { + for _, flg := range category.Flags { + if flg.GetName() == flag.GetName() { + return category.Name + } + } + } + return "MISC" +} + +// NewApp creates an app with sane defaults. +func NewApp(gitCommit, gitDate, usage string) *cli.App { + app := cli.NewApp() + app.Name = filepath.Base(os.Args[0]) + app.Author = "" + app.Email = "" + app.Version = params.VersionWithCommit(gitCommit, gitDate) + app.Usage = usage + return app +} From 6c9f040ebeafcc680b0c457e6f4886e2bca32527 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 14 Jul 2020 21:42:32 +0200 Subject: [PATCH 11/23] core: transaction pool optimizations (#21328) * core: added local tx pool test case * core, crypto: various allocation savings regarding tx handling * core/txlist, txpool: save a reheap operation, avoid some bigint allocs Co-authored-by: Marius van der Wijden --- core/tx_list.go | 106 ++++++++++++++++++++++++++++++++++--------- core/tx_pool.go | 8 +++- core/tx_pool_test.go | 18 ++++++-- 3 files changed, 104 insertions(+), 28 deletions(-) diff --git a/core/tx_list.go b/core/tx_list.go index 164c73006b..bf304eedcf 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -99,7 +99,30 @@ func (m *txSortedMap) Forward(threshold uint64) types.Transactions { // Filter iterates over the list of transactions and removes all of them for which // the specified function evaluates to true. +// Filter, as opposed to 'filter', re-initialises the heap after the operation is done. +// If you want to do several consecutive filterings, it's therefore better to first +// do a .filter(func1) followed by .Filter(func2) or reheap() func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions { + removed := m.filter(filter) + // If transactions were removed, the heap and cache are ruined + if len(removed) > 0 { + m.reheap() + } + return removed +} + +func (m *txSortedMap) reheap() { + *m.index = make([]uint64, 0, len(m.items)) + for nonce := range m.items { + *m.index = append(*m.index, nonce) + } + heap.Init(m.index) + m.cache = nil +} + +// filter is identical to Filter, but **does not** regenerate the heap. This method +// should only be used if followed immediately by a call to Filter or reheap() +func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transactions { var removed types.Transactions // Collect all the transactions to filter out @@ -109,14 +132,7 @@ func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transac delete(m.items, nonce) } } - // If transactions were removed, the heap and cache are ruined if len(removed) > 0 { - *m.index = make([]uint64, 0, len(m.items)) - for nonce := range m.items { - *m.index = append(*m.index, nonce) - } - heap.Init(m.index) - m.cache = nil } return removed @@ -197,10 +213,7 @@ func (m *txSortedMap) Len() int { return len(m.items) } -// Flatten creates a nonce-sorted slice of transactions based on the loosely -// sorted internal representation. The result of the sorting is cached in case -// it's requested again before any modifications are made to the contents. -func (m *txSortedMap) Flatten() types.Transactions { +func (m *txSortedMap) flatten() types.Transactions { // If the sorting was not cached yet, create and cache it if m.cache == nil { m.cache = make(types.Transactions, 0, len(m.items)) @@ -209,12 +222,27 @@ func (m *txSortedMap) Flatten() types.Transactions { } sort.Sort(types.TxByNonce(m.cache)) } + return m.cache +} + +// Flatten creates a nonce-sorted slice of transactions based on the loosely +// sorted internal representation. The result of the sorting is cached in case +// it's requested again before any modifications are made to the contents. +func (m *txSortedMap) Flatten() types.Transactions { // Copy the cache to prevent accidental modifications - txs := make(types.Transactions, len(m.cache)) - copy(txs, m.cache) + cache := m.flatten() + txs := make(types.Transactions, len(cache)) + copy(txs, cache) return txs } +// LastElement returns the last element of a flattened list, thus, the +// transaction with the highest nonce +func (m *txSortedMap) LastElement() *types.Transaction { + cache := m.flatten() + return cache[len(cache)-1] +} + // txList is a "list" of transactions belonging to an account, sorted by account // nonce. The same type can be used both for storing contiguous transactions for // the executable/pending queue; and for storing gapped transactions for the non- @@ -252,7 +280,11 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran // If there's an older better transaction, abort old := l.txs.Get(tx.Nonce()) if old != nil { - threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100)) + // threshold = oldGP * (100 + priceBump) / 100 + a := big.NewInt(100 + int64(priceBump)) + a = a.Mul(a, old.GasPrice()) + b := big.NewInt(100) + threshold := a.Div(a, b) // Have to ensure that the new gas price is higher than the old gas // price as well as checking the percentage threshold to ensure that // this is accurate for low (Wei-level) gas price replacements @@ -296,20 +328,25 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions l.gascap = gasLimit // Filter out all the transactions above the account's funds - removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas() > gasLimit }) + removed := l.txs.Filter(func(tx *types.Transaction) bool { + return tx.Gas() > gasLimit || tx.Cost().Cmp(costLimit) > 0 + }) - // If the list was strict, filter anything above the lowest nonce + if len(removed) == 0 { + return nil, nil + } var invalids types.Transactions - - if l.strict && len(removed) > 0 { + // If the list was strict, filter anything above the lowest nonce + if l.strict { lowest := uint64(math.MaxUint64) for _, tx := range removed { if nonce := tx.Nonce(); lowest > nonce { lowest = nonce } } - invalids = l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest }) + invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest }) } + l.txs.reheap() return removed, invalids } @@ -363,6 +400,12 @@ func (l *txList) Flatten() types.Transactions { return l.txs.Flatten() } +// LastElement returns the last element of a flattened list, thus, the +// transaction with the highest nonce +func (l *txList) LastElement() *types.Transaction { + return l.txs.LastElement() +} + // priceHeap is a heap.Interface implementation over transactions for retrieving // price-sorted transactions to discard when the pool fills up. type priceHeap []*types.Transaction @@ -495,8 +538,29 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo // Discard finds a number of most underpriced transactions, removes them from the // priced list and returns them for further removal from the entire pool. func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions { - drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop - save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep + // If we have some local accountset, those will not be discarded + if !local.empty() { + // In case the list is filled to the brim with 'local' txs, we do this + // little check to avoid unpacking / repacking the heap later on, which + // is very expensive + discardable := 0 + for _, tx := range *l.items { + if !local.containsTx(tx) { + discardable++ + } + if discardable >= slots { + break + } + } + if slots > discardable { + slots = discardable + } + } + if slots == 0 { + return nil + } + drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop + save := make(types.Transactions, 0, len(*l.items)-slots) // Local underpriced transactions to keep for len(*l.items) > 0 && slots > 0 { // Discard stale transactions if found during cleanup diff --git a/core/tx_pool.go b/core/tx_pool.go index 350acc81b4..1636cc5066 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1059,8 +1059,8 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt // Update all accounts to the latest known pending nonce for addr, list := range pool.pending { - txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway - pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1) + highestPending := list.LastElement() + pool.pendingNonces.set(addr, highestPending.Nonce()+1) } pool.mu.Unlock() @@ -1457,6 +1457,10 @@ func (as *accountSet) contains(addr common.Address) bool { return exist } +func (as *accountSet) empty() bool { + return len(as.accounts) == 0 +} + // containsTx checks if the sender of a given tx is within the set. If the sender // cannot be derived, this method returns false. func (as *accountSet) containsTx(tx *types.Transaction) bool { diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 305b3666e6..c436a309f3 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -1890,11 +1890,15 @@ func benchmarkFuturePromotion(b *testing.B, size int) { } // Benchmarks the speed of batched transaction insertion. -func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100) } -func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000) } -func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000) } +func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, false) } +func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, false) } +func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, false) } -func benchmarkPoolBatchInsert(b *testing.B, size int) { +func BenchmarkPoolBatchLocalInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, true) } +func BenchmarkPoolBatchLocalInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, true) } +func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, true) } + +func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { // Generate a batch of transactions to enqueue into the pool pool, key := setupTxPool() defer pool.Stop() @@ -1912,6 +1916,10 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) { // Benchmark importing the transactions into the queue b.ResetTimer() for _, batch := range batches { - pool.AddRemotes(batch) + if local { + pool.AddLocals(batch) + } else { + pool.AddRemotes(batch) + } } } From 240d1851db25f3cca3b324bd9fb7c6edcb2a4e8e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 15 Jul 2020 10:00:04 +0200 Subject: [PATCH 12/23] trie: quell linter in commiter.go (#21329) --- trie/committer.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/trie/committer.go b/trie/committer.go index 00f20827aa..2f3d2a4633 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -105,20 +105,19 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) { // Commit child collapsed := cn.copy() if _, ok := cn.Val.(valueNode); !ok { - if childV, err := c.commit(cn.Val, db, false); err != nil { + childV, err := c.commit(cn.Val, db, false) + if err != nil { return nil, err - } else { - collapsed.Val = childV } + collapsed.Val = childV } // The key needs to be copied, since we're delivering it to database collapsed.Key = hexToCompact(cn.Key) hashedNode := c.store(collapsed, db, force, true) if hn, ok := hashedNode.(hashNode); ok { return hn, nil - } else { - return collapsed, nil } + return collapsed, nil case *fullNode: hashedKids, hasVnodes, err := c.commitChildren(cn, db, force) if err != nil { @@ -130,9 +129,8 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) { hashedNode := c.store(collapsed, db, force, hasVnodes) if hn, ok := hashedNode.(hashNode); ok { return hn, nil - } else { - return collapsed, nil } + return collapsed, nil case valueNode: return c.store(cn, db, force, false), nil // hashnodes aren't stored @@ -265,7 +263,7 @@ func estimateSize(n node) int { if child := n.Children[i]; child != nil { s += estimateSize(child) } else { - s += 1 + s++ } } return s From 295693759e5ded05fec0b2fb39359965b60da785 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 16 Jul 2020 14:06:19 +0200 Subject: [PATCH 13/23] core/vm: less allocations for various call variants (#21222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * core/vm/runtime/tests: add more benchmarks * core/vm: initial work on improving alloc count for calls to precompiles name old time/op new time/op delta SimpleLoop/identity-precompile-10M-6 117ms ±75% 43ms ± 1% -63.09% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 79.6ms ± 4% 70.5ms ± 1% -11.42% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SimpleLoop/identity-precompile-10M-6 24.4MB ± 0% 4.9MB ± 0% -79.94% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 13.2kB ± 0% 13.2kB ± 0% ~ (p=0.357 n=5+5) name old allocs/op new allocs/op delta SimpleLoop/identity-precompile-10M-6 382k ± 0% 153k ± 0% -59.99% (p=0.000 n=5+4) SimpleLoop/loop-10M-6 40.0 ± 0% 40.0 ± 0% ~ (all equal) * core/vm: don't allocate big.int for touch name old time/op new time/op delta SimpleLoop/identity-precompile-10M-6 43.3ms ± 1% 42.4ms ± 7% ~ (p=0.151 n=5+5) SimpleLoop/loop-10M-6 70.5ms ± 1% 76.7ms ± 1% +8.67% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SimpleLoop/identity-precompile-10M-6 4.90MB ± 0% 2.46MB ± 0% -49.83% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 13.2kB ± 0% 13.2kB ± 1% ~ (p=0.571 n=5+5) name old allocs/op new allocs/op delta SimpleLoop/identity-precompile-10M-6 153k ± 0% 76k ± 0% -49.98% (p=0.029 n=4+4) SimpleLoop/loop-10M-6 40.0 ± 0% 40.0 ± 0% ~ (all equal) * core/vm: reduce allocs in staticcall name old time/op new time/op delta SimpleLoop/identity-precompile-10M-6 42.4ms ± 7% 37.5ms ± 6% -11.68% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 76.7ms ± 1% 69.1ms ± 1% -9.82% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SimpleLoop/identity-precompile-10M-6 2.46MB ± 0% 0.02MB ± 0% -99.35% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 13.2kB ± 1% 13.2kB ± 0% ~ (p=0.143 n=5+5) name old allocs/op new allocs/op delta SimpleLoop/identity-precompile-10M-6 76.4k ± 0% 0.1k ± 0% ~ (p=0.079 n=4+5) SimpleLoop/loop-10M-6 40.0 ± 0% 40.0 ± 0% ~ (all equal) * trie: better use of hasher keccakState * core/state/statedb: reduce allocations in getDeletedStateObject * core/vm: reduce allocations in all call derivates * core/vm: reduce allocations in call variants - Make returnstack `uint32` - Use a `sync.Pool` of `stack`s * core/vm: fix tests * core/vm: goimports * core/vm: tracer fix + staticcall gas fix * core/vm: add back snapshot to staticcall * core/vm: review concerns + make returnstack pooled + enable returndata in traces * core/vm: fix some test tracer method signatures * core/vm: run gencodec, minor comment polish Co-authored-by: Péter Szilágyi --- cmd/evm/internal/t8ntool/flags.go | 4 + cmd/evm/internal/t8ntool/transition.go | 7 +- cmd/evm/main.go | 11 ++ cmd/evm/runner.go | 8 +- cmd/evm/staterunner.go | 6 +- core/state/statedb.go | 15 +- core/vm/contracts.go | 17 ++- core/vm/contracts_test.go | 29 ++-- core/vm/evm.go | 193 ++++++++++++++----------- core/vm/gen_structlog.go | 10 +- core/vm/instructions.go | 34 ++++- core/vm/interpreter.go | 13 +- core/vm/logger.go | 35 +++-- core/vm/logger_json.go | 5 +- core/vm/logger_test.go | 2 +- core/vm/runtime/runtime.go | 21 ++- core/vm/runtime/runtime_test.go | 187 ++++++++++++++++++++---- core/vm/stack.go | 34 ++++- eth/tracers/tracer.go | 2 +- eth/tracers/tracer_test.go | 4 +- trie/secure_trie.go | 4 +- 21 files changed, 450 insertions(+), 191 deletions(-) diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index 2e57d72589..d110af2c30 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -38,6 +38,10 @@ var ( Name: "trace.nostack", Usage: "Disable stack output in traces", } + TraceDisableReturnDataFlag = cli.BoolFlag{ + Name: "trace.noreturndata", + Usage: "Disable return data output in traces", + } OutputAllocFlag = cli.StringFlag{ Name: "output.alloc", Usage: "Determines where to put the `alloc` of the post-state.\n" + diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index a4908d763e..079307b975 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -83,9 +83,10 @@ func Main(ctx *cli.Context) error { if ctx.Bool(TraceFlag.Name) { // Configure the EVM logger logConfig := &vm.LogConfig{ - DisableStack: ctx.Bool(TraceDisableStackFlag.Name), - DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), - Debug: true, + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), + DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name), + Debug: true, } var prevFile *os.File // This one closes the last file diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 2d5e9763df..7b472350d9 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -121,6 +121,14 @@ var ( Name: "nostack", Usage: "disable stack output", } + DisableStorageFlag = cli.BoolFlag{ + Name: "nostorage", + Usage: "disable storage output", + } + DisableReturnDataFlag = cli.BoolFlag{ + Name: "noreturndata", + Usage: "disable return data output", + } EVMInterpreterFlag = cli.StringFlag{ Name: "vm.evm", Usage: "External EVM configuration (default = built-in interpreter)", @@ -137,6 +145,7 @@ var stateTransitionCommand = cli.Command{ t8ntool.TraceFlag, t8ntool.TraceDisableMemoryFlag, t8ntool.TraceDisableStackFlag, + t8ntool.TraceDisableReturnDataFlag, t8ntool.OutputAllocFlag, t8ntool.OutputResultFlag, t8ntool.InputAllocFlag, @@ -172,6 +181,8 @@ func init() { ReceiverFlag, DisableMemoryFlag, DisableStackFlag, + DisableStorageFlag, + DisableReturnDataFlag, EVMInterpreterFlag, } app.Commands = []cli.Command{ diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 639f0c0ac9..d0be6ca1e1 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -108,9 +108,11 @@ func runCmd(ctx *cli.Context) error { glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) log.Root().SetHandler(glogger) logconfig := &vm.LogConfig{ - DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), - Debug: ctx.GlobalBool(DebugFlag.Name), + DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name), + DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name), + Debug: ctx.GlobalBool(DebugFlag.Name), } var ( diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 6f9e47cf50..f9a6b06b8f 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -59,8 +59,10 @@ func stateTestCmd(ctx *cli.Context) error { // Configure the EVM logger config := &vm.LogConfig{ - DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name), + DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name), } var ( tracer vm.Tracer diff --git a/core/state/statedb.go b/core/state/statedb.go index 45cf1f7c42..17dd474314 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -505,7 +505,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } // If no live objects are available, attempt to use snapshots var ( - data Account + data *Account err error ) if s.snap != nil { @@ -517,11 +517,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if acc == nil { return nil } - data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash + data = &Account{ + Nonce: acc.Nonce, + Balance: acc.Balance, + CodeHash: acc.CodeHash, + Root: common.BytesToHash(acc.Root), + } if len(data.CodeHash) == 0 { data.CodeHash = emptyCodeHash } - data.Root = common.BytesToHash(acc.Root) if data.Root == (common.Hash{}) { data.Root = emptyRoot } @@ -540,13 +544,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if len(enc) == 0 { return nil } - if err := rlp.DecodeBytes(enc, &data); err != nil { + data = new(Account) + if err := rlp.DecodeBytes(enc, data); err != nil { log.Error("Failed to decode state object", "addr", addr, "err", err) return nil } } // Insert into the live set - obj := newObject(s, addr, data) + obj := newObject(s, addr, *data) s.setStateObject(obj) return obj } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 6493d45895..8930a06266 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -102,12 +102,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{ } // RunPrecompiledContract runs and evaluates the output of a precompiled contract. -func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { - gas := p.RequiredGas(input) - if contract.UseGas(gas) { - return p.Run(input) +// It returns +// - the returned bytes, +// - the _remaining_ gas, +// - any error that occurred +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + gasCost := p.RequiredGas(input) + if suppliedGas < gasCost { + return nil, 0, ErrOutOfGas } - return nil, ErrOutOfGas + suppliedGas -= gasCost + output, err := p.Run(input) + return output, suppliedGas, err } // ECRECOVER implemented as a native contract. @@ -197,6 +203,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { type bigModExp struct{} var ( + big0 = big.NewInt(0) big1 = big.NewInt(1) big4 = big.NewInt(4) big8 = big.NewInt(8) diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 65a9f8b797..5bc365949d 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math/big" "testing" "time" @@ -72,10 +71,9 @@ var blake2FMalformedInputTests = []precompiledFailureTest{ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) - contract := NewContract(AccountRef(common.HexToAddress("1337")), - nil, new(big.Int), p.RequiredGas(in)) - t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) { - if res, err := RunPrecompiledContract(p, in, contract); err != nil { + gas := p.RequiredGas(in) + t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { + if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -91,10 +89,10 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) - contract := NewContract(AccountRef(common.HexToAddress("1337")), - nil, new(big.Int), p.RequiredGas(in)-1) - t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) { - _, err := RunPrecompiledContract(p, in, contract) + gas := p.RequiredGas(in) - 1 + + t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { + _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -109,11 +107,9 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) - contract := NewContract(AccountRef(common.HexToAddress("31337")), - nil, new(big.Int), p.RequiredGas(in)) - + gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, err := RunPrecompiledContract(p, in, contract) + _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -132,8 +128,6 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) reqGas := p.RequiredGas(in) - contract := NewContract(AccountRef(common.HexToAddress("1337")), - nil, new(big.Int), reqGas) var ( res []byte @@ -141,14 +135,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { data = make([]byte, len(in)) ) - bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(bench *testing.B) { + bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) { bench.ReportAllocs() start := time.Now().Nanosecond() bench.ResetTimer() for i := 0; i < bench.N; i++ { - contract.Gas = reqGas copy(data, in) - res, err = RunPrecompiledContract(p, data, contract) + res, _, err = RunPrecompiledContract(p, data, reqGas) } bench.StopTimer() elapsed := float64(time.Now().Nanosecond() - start) diff --git a/core/vm/evm.go b/core/vm/evm.go index 880198bd78..f5469c500c 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -41,23 +42,24 @@ type ( GetHashFunc func(uint64) common.Hash ) +func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { + var precompiles map[common.Address]PrecompiledContract + switch { + case evm.chainRules.IsYoloV1: + precompiles = PrecompiledContractsYoloV1 + case evm.chainRules.IsIstanbul: + precompiles = PrecompiledContractsIstanbul + case evm.chainRules.IsByzantium: + precompiles = PrecompiledContractsByzantium + default: + precompiles = PrecompiledContractsHomestead + } + p, ok := precompiles[addr] + return p, ok +} + // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { - if contract.CodeAddr != nil { - precompiles := PrecompiledContractsHomestead - if evm.chainRules.IsByzantium { - precompiles = PrecompiledContractsByzantium - } - if evm.chainRules.IsIstanbul { - precompiles = PrecompiledContractsIstanbul - } - if evm.chainRules.IsYoloV1 { - precompiles = PrecompiledContractsYoloV1 - } - if p := precompiles[*contract.CodeAddr]; p != nil { - return RunPrecompiledContract(p, input, contract) - } - } for _, interpreter := range evm.interpreters { if interpreter.CanRun(contract.Code) { if evm.interpreter != interpreter { @@ -199,22 +201,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas return nil, gas, ErrDepth } // Fail if we're trying to transfer more than the available balance - if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } - var ( - to = AccountRef(addr) - snapshot = evm.StateDB.Snapshot() - ) + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) + if !evm.StateDB.Exist(addr) { - precompiles := PrecompiledContractsHomestead - if evm.chainRules.IsByzantium { - precompiles = PrecompiledContractsByzantium - } - if evm.chainRules.IsIstanbul { - precompiles = PrecompiledContractsIstanbul - } - if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) @@ -224,35 +218,47 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - // Even if the account has no code, we need to continue because it might be a precompile - start := time.Now() + evm.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - - defer func() { // Lazy evaluation of the parameters - evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) - }() + defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters + evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) + }(gas, time.Now()) } - ret, err = run(evm, contract, input, false) + if isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } + } // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas) + gas = 0 } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) } - return ret, contract.Gas, err + return ret, gas, err } // CallCode executes the contract associated with the addr with the given input @@ -277,23 +283,27 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } - var ( - snapshot = evm.StateDB.Snapshot() - to = AccountRef(caller.Address()) - ) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - ret, err = run(evm, contract, input, false) + var snapshot = evm.StateDB.Snapshot() + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(caller.Address()), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas) + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } // DelegateCall executes the contract associated with the addr with the given input @@ -309,22 +319,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - var ( - snapshot = evm.StateDB.Snapshot() - to = AccountRef(caller.Address()) - ) - // Initialise a new contract and make initialise the delegate values - contract := NewContract(caller, to, nil, gas).AsDelegate() - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - ret, err = run(evm, contract, input, false) + var snapshot = evm.StateDB.Snapshot() + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + addrCopy := addr + // Initialise a new contract and make initialise the delegate values + contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas) + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } // StaticCall executes the contract associated with the addr with the given input @@ -339,32 +353,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - var ( - to = AccountRef(addr) - snapshot = evm.StateDB.Snapshot() - ) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, new(big.Int), gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. + // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced + // after all empty accounts were deleted, so this is not required. However, if we omit this, + // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. + // We could change this, but for now it's left for legacy reasons + var snapshot = evm.StateDB.Snapshot() // We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.StateDB.AddBalance(addr, big.NewInt(0)) - - // When an error was returned by the EVM or when setting the creation code - // above we revert to the snapshot and consume any gas remaining. Additionally - // when we're in Homestead this also counts for code storage gas errors. - ret, err = run(evm, contract, input, true) + evm.StateDB.AddBalance(addr, big0) + + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // At this point, we use a copy of address. If we don't, the go compiler will + // leak the 'contract' to the outer scope, and make allocation for 'contract' + // even if the actual execution ends on RunPrecompiled above. + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in Homestead this also counts for code storage gas errors. + ret, err = run(evm, contract, input, true) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas) + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } type codeAndHash struct { @@ -466,9 +491,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I // // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} - contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) + contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr) } diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index 7ef909954a..ac1a9070c8 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -24,6 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { MemorySize int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -50,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.ReturnStack[k] = math.HexOrDecimal64(v) } } + enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth enc.RefundCounter = s.RefundCounter @@ -70,6 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { MemorySize *int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` @@ -104,11 +107,14 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { } } if dec.ReturnStack != nil { - s.ReturnStack = make([]uint64, len(dec.ReturnStack)) + s.ReturnStack = make([]uint32, len(dec.ReturnStack)) for k, v := range dec.ReturnStack { - s.ReturnStack[k] = uint64(v) + s.ReturnStack[k] = uint32(v) } } + if dec.ReturnData != nil { + s.ReturnData = dec.ReturnData + } if dec.Storage != nil { s.Storage = dec.Storage } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 38d0d09e04..adf44b7f48 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -563,7 +563,7 @@ func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ if !callContext.contract.validJumpSubdest(posU64) { return nil, ErrInvalidJump } - callContext.rstack.push(*pc) + callContext.rstack.push(uint32(*pc)) *pc = posU64 + 1 return nil, nil } @@ -575,7 +575,7 @@ func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) // Other than the check that the return stack is not empty, there is no // need to validate the pc from 'returns', since we only ever push valid //values onto it via jumpsub. - *pc = callContext.rstack.pop() + 1 + *pc = uint64(callContext.rstack.pop()) + 1 return nil, nil } @@ -608,7 +608,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] stackvalue := size callContext.contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig()) + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 + if !value.IsZero() { + bigVal = value.ToBig() + } + + res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -643,8 +649,13 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ callContext.contract.UseGas(gas) // reuse size int for stackvalue stackvalue := size + //TODO: use uint256.Int instead of converting with toBig() + bigEndowment := big0 + if !endowment.IsZero() { + bigEndowment = endowment.ToBig() + } res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, - endowment.ToBig(), salt.ToBig()) + bigEndowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { stackvalue.Clear() @@ -672,10 +683,17 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by // Get the arguments from the memory. args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + var bigVal = big0 + //TODO: use uint256.Int instead of converting with toBig() + // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), + // but it would make more sense to extend the usage of uint256.Int if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value.ToBig()) + + ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal) + if err != nil { temp.Clear() } else { @@ -702,10 +720,14 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( // Get arguments from the memory. args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value.ToBig()) + + ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal) if err != nil { temp.Clear() } else { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9c7c2b4100..89feab0e2f 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -182,13 +182,20 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged bool // deferred Tracer should ignore already logged steps res []byte // result of the opcode execution function ) + // Don't move this deferrred function, it's placed before the capturestate-deferred method, + // so that it get's executed _after_: the capturestate needs the stacks before + // they are returned to the pools + defer func() { + returnStack(stack) + returnRStack(returns) + }() contract.Input = input if in.cfg.Debug { defer func() { if err != nil { if !logged { - in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) } else { in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) } @@ -272,7 +279,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } if in.cfg.Debug { - in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) logged = true } @@ -281,7 +288,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the operation clears the return data (e.g. it has returning data) // set the last return to the result of the operation. if operation.returns { - in.returnData = res + in.returnData = common.CopyBytes(res) } switch { diff --git a/core/vm/logger.go b/core/vm/logger.go index 2c90399aca..e1d7c67ef1 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -47,11 +47,12 @@ func (s Storage) Copy() Storage { // LogConfig are the configuration options for structured logger the EVM type LogConfig struct { - DisableMemory bool // disable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - Debug bool // print output during capture end - Limit int // maximum length of output, but zero means unlimited + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + DisableReturnData bool // disable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited } //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go @@ -66,7 +67,8 @@ type StructLog struct { Memory []byte `json:"memory"` MemorySize int `json:"memSize"` Stack []*big.Int `json:"stack"` - ReturnStack []uint64 `json:"returnStack"` + ReturnStack []uint32 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -104,7 +106,7 @@ func (s *StructLog) ErrorString() string { // if you need to retain them beyond the current call. type Tracer interface { CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error - CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error + CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error } @@ -142,7 +144,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea // CaptureState logs a new structured log message and pushes it out to the environment // // CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { return errTraceLimitReached @@ -161,9 +163,9 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui stck[i] = new(big.Int).Set(item.ToBig()) } } - var rstack []uint64 + var rstack []uint32 if !l.cfg.DisableStack && rStack != nil { - rstck := make([]uint64, len(rStack.data)) + rstck := make([]uint32, len(rStack.data)) copy(rstck, rStack.data) } // Copy a snapshot of the current storage to a new container @@ -192,8 +194,13 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui } storage = l.storage[contract.Address()].Copy() } + var rdata []byte + if !l.cfg.DisableReturnData { + rdata = make([]byte, len(rData)) + copy(rdata, rData) + } // create a new snapshot of the EVM. - log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, storage, depth, env.StateDB.GetRefund(), err} + log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) return nil } @@ -257,6 +264,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) { fmt.Fprintf(writer, "%x: %x\n", h, item) } } + if len(log.ReturnData) > 0 { + fmt.Fprintln(writer, "ReturnData:") + fmt.Fprint(writer, hex.Dump(log.ReturnData)) + } fmt.Fprintln(writer) } } @@ -308,7 +319,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b return nil } -func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) if !t.cfg.DisableStack { // format stack diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index e37c3ce2bd..5f3f2c42f7 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create } // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { log := StructLog{ Pc: pc, Op: op, @@ -70,6 +70,9 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint log.Stack = logstack log.ReturnStack = rStack.data } + if !l.cfg.DisableReturnData { + log.ReturnData = rData + } return l.encoder.Encode(log) } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 572edf9807..e287f0c7aa 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) { stack.push(uint256.NewInt().SetUint64(1)) stack.push(uint256.NewInt()) var index common.Hash - logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, contract, 0, nil) + logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, nil, contract, 0, nil) if len(logger.storage[contract.Address()]) == 0 { t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 9cb492786b..7ebaa9a7e3 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -52,13 +52,20 @@ type Config struct { func setDefaults(cfg *Config) { if cfg.ChainConfig == nil { cfg.ChainConfig = ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: new(big.Int), - DAOForkBlock: new(big.Int), - DAOForkSupport: false, - EIP150Block: new(big.Int), - EIP155Block: new(big.Int), - EIP158Block: new(big.Int), + ChainID: big.NewInt(1), + HomesteadBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, + EIP150Block: new(big.Int), + EIP150Hash: common.Hash{}, + EIP155Block: new(big.Int), + EIP158Block: new(big.Int), + ByzantiumBlock: new(big.Int), + ConstantinopleBlock: new(big.Int), + PetersburgBlock: new(big.Int), + IstanbulBlock: new(big.Int), + MuirGlacierBlock: new(big.Int), + YoloV1Block: nil, } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 991813bf81..108ee80e41 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -321,34 +321,6 @@ func TestBlockhash(t *testing.T) { } } -// BenchmarkSimpleLoop test a pretty simple loop which loops -// 1M (1 048 575) times. -// Takes about 200 ms -func BenchmarkSimpleLoop(b *testing.B) { - // 0xfffff = 1048575 loops - code := []byte{ - byte(vm.PUSH3), 0x0f, 0xff, 0xff, - byte(vm.JUMPDEST), // [ count ] - byte(vm.PUSH1), 1, // [count, 1] - byte(vm.SWAP1), // [1, count] - byte(vm.SUB), // [ count -1 ] - byte(vm.DUP1), // [ count -1 , count-1] - byte(vm.PUSH1), 4, // [count-1, count -1, label] - byte(vm.JUMPI), // [ 0 ] - byte(vm.STOP), - } - //tracer := vm.NewJSONLogger(nil, os.Stdout) - //Execute(code, nil, &Config{ - // EVMConfig: vm.Config{ - // Debug: true, - // Tracer: tracer, - // }}) - - for i := 0; i < b.N; i++ { - Execute(code, nil, nil) - } -} - type stepCounter struct { inner *vm.JSONLogger steps int @@ -358,7 +330,7 @@ func (s *stepCounter) CaptureStart(from common.Address, to common.Address, creat return nil } -func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { +func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rData []byte, contract *vm.Contract, depth int, err error) error { s.steps++ // Enable this for more output //s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err) @@ -593,3 +565,160 @@ func DisabledTestEipExampleCases(t *testing.T) { "allowed, and causes an error", code) } } + +// benchmarkNonModifyingCode benchmarks code, but if the code modifies the +// state, this should not be used, since it does not reset the state between runs. +func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) { + cfg := new(Config) + setDefaults(cfg) + cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + cfg.GasLimit = gas + var ( + destination = common.BytesToAddress([]byte("contract")) + vmenv = NewEnv(cfg) + sender = vm.AccountRef(cfg.Origin) + ) + cfg.State.CreateAccount(destination) + eoa := common.HexToAddress("E0") + { + cfg.State.CreateAccount(eoa) + cfg.State.SetNonce(eoa, 100) + } + reverting := common.HexToAddress("EE") + { + cfg.State.CreateAccount(reverting) + cfg.State.SetCode(reverting, []byte{ + byte(vm.PUSH1), 0x00, + byte(vm.PUSH1), 0x00, + byte(vm.REVERT), + }) + } + + //cfg.State.CreateAccount(cfg.Origin) + // set the receiver's (the executing contract) code for execution. + cfg.State.SetCode(destination, code) + vmenv.Call(sender, destination, nil, gas, cfg.Value) + + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + vmenv.Call(sender, destination, nil, gas, cfg.Value) + } + }) +} + +// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG +// 55 ms +func BenchmarkSimpleLoop(b *testing.B) { + + staticCallIdentity := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.PUSH1), 0x4, // address of identity + byte(vm.GAS), // gas + byte(vm.STATICCALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callIdentity := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // value + byte(vm.PUSH1), 0x4, // address of identity + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callInexistant := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // value + byte(vm.PUSH1), 0xff, // address of existing contract + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callEOA := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // value + byte(vm.PUSH1), 0xE0, // address of EOA + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + loopingCode := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.PUSH1), 0x4, // address of identity + byte(vm.GAS), // gas + + byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + calllRevertingContractWithInput := []byte{ + byte(vm.JUMPDEST), // + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.PUSH1), 0x20, // in size + byte(vm.PUSH1), 0x00, // in offset + byte(vm.PUSH1), 0x00, // value + byte(vm.PUSH1), 0xEE, // address of reverting contract + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + //tracer := vm.NewJSONLogger(nil, os.Stdout) + //Execute(loopingCode, nil, &Config{ + // EVMConfig: vm.Config{ + // Debug: true, + // Tracer: tracer, + // }}) + // 100M gas + benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", b) + benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", b) + benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b) + benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", b) + benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", b) + benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", b) + + //benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b) + //benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b) +} diff --git a/core/vm/stack.go b/core/vm/stack.go index 99de4d79c8..af27d6552c 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -18,10 +18,17 @@ package vm import ( "fmt" + "sync" "github.com/holiman/uint256" ) +var stackPool = sync.Pool{ + New: func() interface{} { + return &Stack{data: make([]uint256.Int, 0, 16)} + }, +} + // Stack is an object for basic stack operations. Items popped to the stack are // expected to be changed and modified. stack does not take care of adding newly // initialised objects. @@ -30,7 +37,12 @@ type Stack struct { } func newstack() *Stack { - return &Stack{data: make([]uint256.Int, 0, 16)} + return stackPool.Get().(*Stack) +} + +func returnStack(s *Stack) { + s.data = s.data[:0] + stackPool.Put(s) } // Data returns the underlying uint256.Int array. @@ -87,20 +99,32 @@ func (st *Stack) Print() { fmt.Println("#############") } +var rStackPool = sync.Pool{ + New: func() interface{} { + return &ReturnStack{data: make([]uint32, 0, 10)} + }, +} + // ReturnStack is an object for basic return stack operations. type ReturnStack struct { - data []uint64 + data []uint32 } func newReturnStack() *ReturnStack { - return &ReturnStack{data: make([]uint64, 0, 1024)} + return rStackPool.Get().(*ReturnStack) +} + +func returnRStack(rs *ReturnStack) { + rs.data = rs.data[:0] + rStackPool.Put(rs) } -func (st *ReturnStack) push(d uint64) { +func (st *ReturnStack) push(d uint32) { st.data = append(st.data, d) } -func (st *ReturnStack) pop() (ret uint64) { +// A uint32 is sufficient as for code below 4.2G +func (st *ReturnStack) pop() (ret uint32) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index a1394920f4..050fb05159 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -541,7 +541,7 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { +func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rdata []byte, contract *vm.Contract, depth int, err error) error { if jst.err == nil { // Initialize the context if it wasn't done yet if !jst.inited { diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 911431a3fd..b4de998651 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -169,10 +169,10 @@ func TestHaltBetweenSteps(t *testing.T) { env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) + tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) + tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) if _, err := tracer.GetResult(); err.Error() != timeout.Error() { t.Errorf("Expected timeout error, got %v", err) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 955771495b..bd8e51d989 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -179,9 +179,9 @@ func (t *SecureTrie) hashKey(key []byte) []byte { h := newHasher(false) h.sha.Reset() h.sha.Write(key) - buf := h.sha.Sum(t.hashKeyBuf[:0]) + h.sha.Read(t.hashKeyBuf[:]) returnHasherToPool(h) - return buf + return t.hashKeyBuf[:] } // getSecKeyCache returns the current secure key cache, creating a new one if From 9e88224eb86c92c78f5f03ffcd0793704a8b4309 Mon Sep 17 00:00:00 2001 From: Nikola Madjarevic Date: Thu, 16 Jul 2020 14:08:38 +0200 Subject: [PATCH 14/23] core: raise gas limit in --dev mode, seed blake precompile (#21323) * Set gasLimit in --dev mode to be 9m. * core: Set gasLimit to 11.5 milion and add 1 wei allocation for BLAKE2b --- core/genesis.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/genesis.go b/core/genesis.go index afaa29c428..d2a8a4798b 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -400,7 +400,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis { return &Genesis{ Config: &config, ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...), - GasLimit: 6283185, + GasLimit: 11500000, Difficulty: big.NewInt(1), Alloc: map[common.Address]GenesisAccount{ common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover @@ -411,6 +411,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis { common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing + common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))}, }, } From 0fef66c739c4a359ac438414d502d21ff1510fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 20 Jul 2020 11:11:38 +0300 Subject: [PATCH 15/23] ethstats: fix reconnection issue, implement primus pings --- ethstats/ethstats.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 4d2c2edb6b..b60ac56eab 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -280,8 +280,10 @@ func (s *Service) loop() { } } fullReport.Stop() - // Make sure the connection is closed + + // Close the current connection and establish a new one conn.Close() + errTimer.Reset(0) } } } @@ -296,8 +298,23 @@ func (s *Service) readLoop(conn *websocket.Conn) { for { // Retrieve the next generic network packet and bail out on error + var blob json.RawMessage + if err := conn.ReadJSON(&blob); err != nil { + log.Warn("Failed to retrieve stats server message", "err", err) + return + } + // If the network packet is a system ping, respond to it directly + var ping string + if err := json.Unmarshal(blob, &ping); err == nil && strings.HasPrefix(ping, "primus::ping::") { + if err := conn.WriteJSON(strings.Replace(ping, "ping", "pong", -1)); err != nil { + log.Warn("Failed to respond to system ping message", "err", err) + return + } + continue + } + // Not a system ping, try to decode an actual state message var msg map[string][]interface{} - if err := conn.ReadJSON(&msg); err != nil { + if err := json.Unmarshal(blob, &msg); err != nil { log.Warn("Failed to decode stats server message", "err", err) return } From 43e2e58cbda97c22ddbb616cb74a03c7bc916833 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 20 Jul 2020 20:52:42 +0800 Subject: [PATCH 16/23] accounts, internal: fix funding check when estimating gas (#21346) * internal, accounts: fix funding check when estimate gas * accounts, internal: address comments --- accounts/abi/bind/backends/simulated.go | 4 ++-- internal/ethapi/api.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 0783b586e1..4b9372a201 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -448,7 +448,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs hi = b.pendingBlock.GasLimit() } // Recap the highest gas allowance with account's balance. - if call.GasPrice != nil && call.GasPrice.Uint64() != 0 { + if call.GasPrice != nil && call.GasPrice.BitLen() != 0 { balance := b.pendingState.GetBalance(call.From) // from can't be nil available := new(big.Int).Set(balance) if call.Value != nil { @@ -458,7 +458,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs available.Sub(available, call.Value) } allowance := new(big.Int).Div(available, call.GasPrice) - if hi > allowance.Uint64() { + if allowance.IsUint64() && hi > allowance.Uint64() { transfer := call.Value if transfer == nil { transfer = new(big.Int) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ac0b7bed38..c035b7a9ed 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -966,7 +966,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash hi = block.GasLimit() } // Recap the highest gas limit with account's available balance. - if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 { + if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 { state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if err != nil { return 0, err @@ -980,7 +980,9 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash available.Sub(available, args.Value.ToInt()) } allowance := new(big.Int).Div(available, args.GasPrice.ToInt()) - if hi > allowance.Uint64() { + + // If the allowance is larger than maximum uint64, skip checking + if allowance.IsUint64() && hi > allowance.Uint64() { transfer := args.Value if transfer == nil { transfer = new(hexutil.Big) From 748f22c192d24082723f935afd0b0b63e7fd50f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 20 Jul 2020 15:56:42 +0300 Subject: [PATCH 17/23] params: release Geth v1.9.17 --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 5fb0e1fd9f..c66775d324 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 17 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 17 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 3863cf547ccf9e39c542e891bb7c7999fa80625a Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Tue, 23 Feb 2021 16:08:59 +0000 Subject: [PATCH 18/23] Merge with latest quorum master --- .bintray.json | 37 + .github/CONTRIBUTING.md | 28 +- .github/ISSUE_TEMPLATE.md | 4 +- .github/workflows/build.yml | 273 +++ .github/workflows/pr.yml | 116 ++ .github/workflows/release.yml | 365 ++++ .gitignore | 5 + .golangci.yml | 2 +- BUILDING.md | 33 + Dockerfile | 5 +- Makefile | 5 + NOTES.md | 112 ++ README.md | 397 +---- accounts/abi/bind/auth.go | 16 + accounts/abi/bind/backend.go | 4 +- accounts/abi/bind/backends/simulated.go | 68 +- accounts/abi/bind/backends/simulated_test.go | 22 +- accounts/abi/bind/base.go | 50 +- accounts/abi/bind/bind_test.go | 7 + accounts/abi/bind/template.go | 3 + accounts/abi/bind/util_test.go | 6 +- accounts/external/backend.go | 15 +- accounts/keystore/account_cache_test.go | 2 +- accounts/keystore/keystore.go | 11 + accounts/manager.go | 16 + accounts/pluggable/backend.go | 74 + accounts/pluggable/backend_test.go | 131 ++ .../pluggable/internal/testutils/matchers.go | 19 + .../testutils/mock_plugin/mock_plugin.go | 196 +++ accounts/pluggable/wallet.go | 129 ++ accounts/pluggable/wallet_test.go | 397 +++++ accounts/usbwallet/wallet.go | 5 + build/ci.go | 6 +- build/install-constellation-linux.sh | 7 + build/install-constellation-mac.sh | 15 + build/travis-install-linux.sh | 42 + build/travis-run-acceptance-tests-linux.sh | 20 + cmd/clef/README.md | 5 +- cmd/clef/main.go | 135 +- cmd/evm/internal/t8ntool/execution.go | 21 +- cmd/faucet/faucet.go | 4 +- cmd/geth/accountcmd.go | 1 + cmd/geth/accountcmd_plugin.go | 323 ++++ cmd/geth/accountcmd_plugin_test.go | 299 ++++ cmd/geth/accountcmd_test.go | 52 +- cmd/geth/chaincmd.go | 42 +- cmd/geth/config.go | 112 +- cmd/geth/consolecmd.go | 141 +- cmd/geth/consolecmd_test.go | 181 +- cmd/geth/dao_test.go | 1 + cmd/geth/genesis_test.go | 105 +- cmd/geth/les_test.go | 4 + cmd/geth/main.go | 116 +- cmd/geth/misccmd.go | 2 + cmd/geth/retesteth.go | 34 +- cmd/geth/testdata/clique.json | 3 +- cmd/geth/testdata/geth/nodekey | 1 + cmd/geth/testdata/geth/static-nodes.json | 3 + cmd/geth/usage.go | 74 + cmd/puppeth/genesis_test.go | 8 + cmd/utils/flags.go | 402 ++++- cmd/utils/flags_test.go | 93 + common/http/certificate.go | 61 + common/http/client.go | 49 + common/http/config.go | 206 +++ common/http/config_test.go | 342 ++++ common/http/transport.go | 55 + common/slice.go | 54 + common/slice_test.go | 93 + common/types.go | 141 ++ common/types_test.go | 77 + consensus/clique/api.go | 46 +- consensus/clique/clique.go | 12 +- consensus/consensus.go | 27 + consensus/ethash/consensus.go | 19 +- consensus/ethash/ethash.go | 8 + consensus/istanbul/backend.go | 75 + consensus/istanbul/backend/api.go | 272 +++ consensus/istanbul/backend/backend.go | 319 ++++ consensus/istanbul/backend/backend_test.go | 239 +++ consensus/istanbul/backend/engine.go | 758 ++++++++ consensus/istanbul/backend/engine_test.go | 579 ++++++ consensus/istanbul/backend/handler.go | 138 ++ consensus/istanbul/backend/handler_test.go | 181 ++ consensus/istanbul/backend/snapshot.go | 321 ++++ consensus/istanbul/backend/snapshot_test.go | 455 +++++ consensus/istanbul/config.go | 44 + consensus/istanbul/core/backlog.go | 188 ++ consensus/istanbul/core/backlog_test.go | 364 ++++ consensus/istanbul/core/commit.go | 107 ++ consensus/istanbul/core/commit_test.go | 325 ++++ consensus/istanbul/core/core.go | 359 ++++ consensus/istanbul/core/core_test.go | 97 + consensus/istanbul/core/errors.go | 48 + consensus/istanbul/core/events.go | 28 + consensus/istanbul/core/final_committed.go | 26 + consensus/istanbul/core/handler.go | 201 +++ consensus/istanbul/core/handler_test.go | 127 ++ consensus/istanbul/core/message_set.go | 115 ++ consensus/istanbul/core/message_set_test.go | 106 ++ consensus/istanbul/core/prepare.go | 95 + consensus/istanbul/core/prepare_test.go | 359 ++++ consensus/istanbul/core/preprepare.go | 129 ++ consensus/istanbul/core/preprepare_test.go | 298 ++++ consensus/istanbul/core/request.go | 99 ++ consensus/istanbul/core/request_test.go | 138 ++ consensus/istanbul/core/roundchange.go | 172 ++ consensus/istanbul/core/roundchange_test.go | 92 + consensus/istanbul/core/roundstate.go | 221 +++ consensus/istanbul/core/roundstate_test.go | 76 + consensus/istanbul/core/testbackend_test.go | 291 +++ consensus/istanbul/core/types.go | 180 ++ consensus/istanbul/core/types_test.go | 180 ++ consensus/istanbul/errors.go | 29 + consensus/istanbul/events.go | 31 + consensus/istanbul/types.go | 147 ++ consensus/istanbul/types_test.go | 71 + consensus/istanbul/utils.go | 60 + consensus/istanbul/validator.go | 80 + consensus/istanbul/validator/default.go | 201 +++ consensus/istanbul/validator/default_test.go | 209 +++ consensus/istanbul/validator/validator.go | 47 + consensus/protocol.go | 77 + console/console.go | 54 +- .../develop-alpine/Dockerfile_BASE_30347 | 14 + .../develop-alpine/Dockerfile_LOCAL_30347 | 14 + containers/vagrant/.gitignore | 1 + .../vagrant/provisioners/shell/centos.sh | 11 + .../vagrant/provisioners/shell/debian.sh | 11 + .../vagrant/provisioners/shell/ubuntu.sh | 11 + core/bench_test.go | 1 + core/block_validator.go | 6 +- core/blockchain.go | 229 ++- core/blockchain_test.go | 24 +- core/call_helper.go | 100 ++ core/chain_makers.go | 2 +- core/chain_makers_test.go | 2 +- core/constellation-test-keys/tm1.key | 1 + core/constellation-test-keys/tm1.pub | 1 + core/constellation-test-keys/tm1a.key | 1 + core/constellation-test-keys/tm1a.pub | 1 + core/dual_state_test.go | 213 +++ core/error.go | 17 + core/events.go | 8 + core/evm.go | 26 + core/forkid/forkid.go | 5 + core/genesis.go | 33 +- core/genesis_test.go | 61 +- core/private_state_test.go | 146 ++ core/rawdb/database_quorum.go | 124 ++ core/rawdb/database_quorum_test.go | 153 ++ core/rawdb/freezer.go | 19 +- core/rawdb/freezer_test.go | 75 + core/state/account_extra_data.go | 160 ++ core/state/account_extra_data_test.go | 193 ++ core/state/database.go | 25 +- core/state/dump.go | 30 + core/state/journal.go | 17 +- core/state/state_object.go | 151 ++ core/state/state_test.go | 58 + core/state/statedb.go | 182 +- core/state/statedb_test.go | 255 +++ core/state_prefetcher.go | 13 +- core/state_processor.go | 87 +- core/state_transition.go | 218 ++- core/state_transition_pmh.go | 136 ++ core/state_transition_pmh_test.go | 53 + core/state_transition_test.go | 1363 ++++++++++++++ core/tx_pool.go | 89 +- core/tx_pool_test.go | 238 ++- core/types.go | 4 +- core/types/block.go | 12 + core/types/bloom9.go | 9 + core/types/istanbul.go | 107 ++ core/types/istanbul_test.go | 88 + core/types/transaction.go | 152 +- core/types/transaction_signing.go | 13 +- core/types/transaction_signing_private.go | 61 + ...transaction_signing_quorum_private_test.go | 62 + core/types/transaction_signing_quorum_test.go | 354 ++++ core/types/transaction_signing_test.go | 33 +- core/types/transaction_test.go | 24 + core/vm/errors.go | 3 + core/vm/evm.go | 500 +++++- core/vm/evm_test.go | 23 + core/vm/gas_table.go | 7 +- core/vm/gas_table_test.go | 2 +- core/vm/instructions.go | 33 +- core/vm/instructions_test.go | 14 +- core/vm/interface.go | 58 +- core/vm/interpreter.go | 8 + core/vm/logger_test.go | 3 +- core/vm/mock_interface.go | 957 ++++++++++ core/vm/runtime/env.go | 2 +- core/vm/runtime/evm_privacy_test.go | 448 +++++ core/vm/runtime/runtime_test.go | 7 + crypto/bn256/cloudflare/gfp_decl.go | 1 - crypto/crypto.go | 2 +- crypto/secp256k1/go.mod | 3 + docs/Quorum Design.png | Bin 0 -> 76981 bytes docs/Quorum Whitepaper v0.2.pdf | Bin 0 -> 1887682 bytes docs/conf.py | 2 + docs/images/ContractDesign.png | Bin 0 -> 816382 bytes docs/images/PermissionsModel.png | Bin 0 -> 348517 bytes docs/images/logo-48x48.png | Bin 0 -> 1717 bytes docs/images/logo.png | Bin 0 -> 206848 bytes docs/theme/404.html | 4 + .../javascripts/application.81068b3a.js | 6 + .../stylesheets/application.668e8dde.css | 1 + docs/theme/assets/stylesheets/extra.css | 15 + docs/theme/base.html | 220 +++ docs/theme/partials/footer.html | 80 + docs/theme/partials/header.html | 87 + docs/theme/partials/nav.html | 46 + eth/api.go | 89 +- eth/api_backend.go | 239 ++- eth/api_tracer.go | 150 +- eth/backend.go | 141 +- eth/bloombits.go | 10 +- eth/config.go | 23 +- eth/config_test.go | 21 + eth/downloader/downloader.go | 244 ++- eth/downloader/downloader_test.go | 4 + eth/downloader/modes.go | 2 + eth/downloader/peer.go | 3 +- eth/filters/api.go | 50 +- eth/filters/filter.go | 14 +- eth/filters/filter_system_test.go | 21 +- eth/filters/filter_test.go | 36 + eth/gen_config.go | 7 + eth/handler.go | 141 +- eth/handler_test.go | 48 +- eth/helper_test.go | 61 +- eth/peer.go | 58 +- eth/protocol.go | 2 +- eth/protocol_test.go | 20 +- eth/quorum_protocol.go | 209 +++ eth/sync.go | 16 +- eth/sync_test.go | 4 +- eth/tracers/tracer_test.go | 7 +- eth/tracers/tracers_test.go | 4 +- ethclient/ethclient.go | 44 +- ethclient/ethclient_test.go | 29 + ethclient/privateTransactionManagerClient.go | 73 + .../privateTransactionManagerClient_test.go | 56 + ethstats/ethstats.go | 4 +- extension/api.go | 385 ++++ extension/backend.go | 437 +++++ extension/backend_test.go | 211 +++ extension/client.go | 46 + extension/contract_facade.go | 61 + extension/data_handler.go | 55 + extension/data_handler_test.go | 44 + .../extensionContracts/contract_extender.go | 1559 +++++++++++++++++ .../extensionContracts/contract_extender.sol | 168 ++ .../extensionContracts/extensionHandler.go | 18 + extension/extensionContracts/types.go | 9 + extension/extension_utilities.go | 38 + .../privacyExtension/state_set_utilities.go | 97 + .../state_set_utilities_test.go | 151 ++ extension/privacyExtension/state_setter.go | 157 ++ extension/services_factory.go | 65 + extension/state_fetcher.go | 120 ++ extension/state_fetcher_test.go | 49 + extension/types.go | 40 + go.mod | 43 +- go.sum | 135 +- graphql/graphql.go | 48 +- graphql/graphql_test.go | 88 + graphql/schema.go | 4 + internal/build/gosrc.go | 81 + internal/build/util.go | 50 + internal/build/util_test.go | 40 + internal/ethapi/api.go | 914 +++++++++- internal/ethapi/api_test.go | 745 ++++++++ internal/ethapi/backend.go | 15 +- internal/plugin/protocol.go | 21 + internal/web3ext/web3ext.go | 444 ++++- les/api_backend.go | 43 +- les/client.go | 20 +- les/odr_test.go | 4 +- les/server_handler.go | 5 +- les/test_helper.go | 16 +- light/lightchain.go | 20 +- light/odr_test.go | 2 +- light/trie.go | 21 + log/emit_checkpoint.go | 22 + logo.png | Bin 0 -> 8324 bytes miner/miner.go | 23 +- miner/worker.go | 168 +- miner/worker_test.go | 1 + mkdocs.yml | 187 ++ mobile/ethclient.go | 4 +- multitenancy/authorization_provider.go | 222 +++ multitenancy/authorization_provider_test.go | 861 +++++++++ multitenancy/types.go | 200 +++ node/api.go | 13 +- node/config.go | 46 + node/config_test.go | 92 + node/endpoints.go | 62 +- node/node.go | 103 +- node/node_test.go | 3 +- node/service.go | 6 + p2p/enode/node.go | 95 +- p2p/enode/node_test.go | 124 ++ p2p/enode/urlv4.go | 108 +- p2p/enode/urlv4_test.go | 15 + p2p/enr/entries.go | 10 + p2p/peer.go | 15 + p2p/peer_error.go | 13 + p2p/server.go | 67 + p2p/server_test.go | 73 + params/config.go | 252 ++- params/config_test.go | 210 ++- params/network_params.go | 20 + params/network_params_test.go | 17 + params/protocol_params.go | 23 +- params/protocol_params_test.go | 25 + params/quorum.go | 12 + params/version.go | 12 + permission/api.go | 1095 ++++++++++++ permission/backend.go | 284 +++ permission/connection.go | 65 + permission/core/cache.go | 634 +++++++ permission/core/cache_test.go | 433 +++++ permission/core/permissions.go | 127 ++ permission/core/permissions_test.go | 132 ++ permission/core/types/backend.go | 350 ++++ permission/core/types/contract.go | 130 ++ permission/permission.go | 416 +++++ permission/permission_test.go | 1002 +++++++++++ permission/v1/backend.go | 368 ++++ permission/v1/bind/accounts.go | 935 ++++++++++ permission/v1/bind/nodes.go | 1356 ++++++++++++++ permission/v1/bind/org.go | 1075 ++++++++++++ permission/v1/bind/permission_impl.go | 983 +++++++++++ permission/v1/bind/permission_interface.go | 840 +++++++++ permission/v1/bind/permission_upgr.go | 313 ++++ permission/v1/bind/roles.go | 718 ++++++++ permission/v1/bind/voter.go | 853 +++++++++ permission/v1/contract.go | 372 ++++ permission/v1/contract/AccountManager.sol | 362 ++++ permission/v1/contract/NodeManager.sol | 257 +++ permission/v1/contract/OrgManager.sol | 371 ++++ .../v1/contract/PermissionsImplementation.sol | 662 +++++++ .../v1/contract/PermissionsInterface.sol | 302 ++++ .../v1/contract/PermissionsUpgradable.sol | 103 ++ permission/v1/contract/RoleManager.sol | 199 +++ permission/v1/contract/VoterManager.sol | 250 +++ permission/v1/contract/gen/AccountManager.abi | 1 + permission/v1/contract/gen/AccountManager.bin | 1 + permission/v1/contract/gen/NodeManager.abi | 1 + permission/v1/contract/gen/NodeManager.bin | 1 + permission/v1/contract/gen/OrgManager.abi | 1 + permission/v1/contract/gen/OrgManager.bin | 1 + .../gen/PermissionsImplementation.abi | 1 + .../gen/PermissionsImplementation.bin | 1 + .../v1/contract/gen/PermissionsInterface.abi | 1 + .../v1/contract/gen/PermissionsInterface.bin | 1 + .../v1/contract/gen/PermissionsUpgradable.abi | 1 + .../v1/contract/gen/PermissionsUpgradable.bin | 1 + permission/v1/contract/gen/RoleManager.abi | 1 + permission/v1/contract/gen/RoleManager.bin | 1 + permission/v1/contract/gen/VoterManager.abi | 1 + permission/v1/contract/gen/VoterManager.bin | 1 + permission/v1/contract/gen/gen.go | 27 + permission/v2/backend.go | 348 ++++ permission/v2/bind/accounts.go | 991 +++++++++++ permission/v2/bind/nodes.go | 1427 +++++++++++++++ permission/v2/bind/org.go | 1101 ++++++++++++ permission/v2/bind/permission_impl.go | 1035 +++++++++++ permission/v2/bind/permission_interface.go | 892 ++++++++++ permission/v2/bind/permission_upgr.go | 313 ++++ permission/v2/bind/roles.go | 770 ++++++++ permission/v2/bind/voter.go | 853 +++++++++ permission/v2/contract.go | 408 +++++ permission/v2/contract/AccountManager.sol | 377 ++++ permission/v2/contract/NodeManager.sol | 310 ++++ permission/v2/contract/OrgManager.sol | 390 +++++ .../v2/contract/PermissionsImplementation.sol | 760 ++++++++ .../v2/contract/PermissionsInterface.sol | 351 ++++ .../v2/contract/PermissionsUpgradable.sol | 103 ++ permission/v2/contract/RoleManager.sol | 236 +++ permission/v2/contract/VoterManager.sol | 250 +++ permission/v2/contract/gen/AccountManager.abi | 1 + permission/v2/contract/gen/AccountManager.bin | 1 + permission/v2/contract/gen/NodeManager.abi | 1 + permission/v2/contract/gen/NodeManager.bin | 1 + permission/v2/contract/gen/OrgManager.abi | 1 + permission/v2/contract/gen/OrgManager.bin | 1 + .../gen/PermissionsImplementation.abi | 1 + .../gen/PermissionsImplementation.bin | 1 + .../v2/contract/gen/PermissionsInterface.abi | 1 + .../v2/contract/gen/PermissionsInterface.bin | 1 + .../v2/contract/gen/PermissionsUpgradable.abi | 1 + .../v2/contract/gen/PermissionsUpgradable.bin | 1 + permission/v2/contract/gen/RoleManager.abi | 1 + permission/v2/contract/gen/RoleManager.bin | 1 + permission/v2/contract/gen/VoterManager.abi | 1 + permission/v2/contract/gen/VoterManager.bin | 1 + permission/v2/contract/gen/gen.go | 27 + plugin/account/connector.go | 26 + plugin/account/creator.go | 25 + plugin/account/gateway.go | 213 +++ plugin/account/gateway_test.go | 338 ++++ plugin/account/internal/testutils/matchers.go | 184 ++ plugin/account/reloadableimpl.go | 105 ++ plugin/account/service.go | 26 + plugin/api.go | 15 + plugin/base.go | 291 +++ plugin/central.go | 145 ++ plugin/central_test.go | 114 ++ plugin/downloader.go | 34 + plugin/downloader_test.go | 36 + plugin/gen/docs.markdown.tmpl | 76 + plugin/gen/gen.go | 27 + plugin/gen/proto_common/init.pb.go | 247 +++ plugin/gen/proto_common/mock_init.go | 94 + plugin/helloworld/connector.go | 26 + plugin/helloworld/gateway.go | 21 + plugin/helloworld/gateway_test.go | 32 + plugin/helloworld/service.go | 21 + plugin/initializer/connector.go | 26 + plugin/initializer/gateway.go | 19 + plugin/initializer/gateway_test.go | 32 + plugin/initializer/service.go | 7 + plugin/local_verifier.go | 52 + plugin/local_verifier_test.go | 39 + plugin/online_verifier.go | 23 + plugin/plugin_templates.go | 98 ++ plugin/security/connector.go | 43 + plugin/security/gateway.go | 88 + plugin/security/gateway_test.go | 157 ++ plugin/security/service.go | 61 + plugin/security/utils.go | 57 + plugin/service.go | 238 +++ plugin/service_test.go | 144 ++ plugin/settings.go | 290 +++ plugin/settings_test.go | 276 +++ plugin/utils.go | 173 ++ plugin/utils_test.go | 342 ++++ plugin/verifier.go | 45 + plugin/verifier_test.go | 40 + private/cache/cache.go | 22 + private/engine/common.go | 107 ++ private/engine/common_test.go | 71 + private/engine/constellation/constellation.go | 115 ++ private/engine/constellation/node.go | 77 + .../notinuse/notInUsePrivateTxManager.go | 59 + .../notinuse/notInUsePrivateTxManager_test.go | 58 + private/engine/tessera/model.go | 94 + private/engine/tessera/tessera.go | 449 +++++ private/engine/tessera/tessera_test.go | 451 +++++ .../engine/tessera/tessera_version_checker.go | 76 + .../tessera/tessera_version_checker_test.go | 78 + .../engine/tessera/tessera_version_reader.go | 55 + .../tessera/tessera_version_reader_test.go | 121 ++ private/private.go | 122 ++ private/private_test.go | 106 ++ raft/api.go | 134 ++ raft/backend.go | 107 ++ raft/constants.go | 35 + raft/events.go | 13 + raft/handler.go | 1098 ++++++++++++ raft/handler_test.go | 196 +++ raft/listener.go | 59 + raft/minter.go | 446 +++++ raft/minter_test.go | 208 +++ raft/peer.go | 116 ++ raft/persistence.go | 57 + raft/snapshot.go | 393 +++++ raft/speculative_chain.go | 187 ++ raft/util.go | 30 + raft/wal.go | 54 + rpc/client.go | 16 + rpc/client_test.go | 53 + rpc/handler.go | 18 +- rpc/http.go | 29 +- rpc/inproc.go | 3 + rpc/json.go | 13 + rpc/security.go | 117 ++ rpc/security_test.go | 219 +++ rpc/server.go | 45 +- rpc/server_test.go | 124 ++ rpc/testservice_test.go | 4 + rpc/types.go | 6 + rpc/types_test.go | 2 +- rpc/websocket.go | 40 +- rpc/websocket_test.go | 2 +- signer/core/api.go | 24 +- signer/core/api_test.go | 2 +- signer/core/plugin_api.go | 39 + signer/core/types.go | 14 +- tests/block_test.go | 6 + tests/block_test_util.go | 2 +- tests/state_test_util.go | 2 +- tests/transaction_test.go | 17 + tests/vm_test_util.go | 2 +- trie/database.go | 3 + 499 files changed, 69347 insertions(+), 1218 deletions(-) create mode 100644 .bintray.json create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/release.yml create mode 100644 BUILDING.md create mode 100644 NOTES.md create mode 100644 accounts/pluggable/backend.go create mode 100644 accounts/pluggable/backend_test.go create mode 100644 accounts/pluggable/internal/testutils/matchers.go create mode 100644 accounts/pluggable/internal/testutils/mock_plugin/mock_plugin.go create mode 100644 accounts/pluggable/wallet.go create mode 100644 accounts/pluggable/wallet_test.go create mode 100755 build/install-constellation-linux.sh create mode 100755 build/install-constellation-mac.sh create mode 100755 build/travis-install-linux.sh create mode 100755 build/travis-run-acceptance-tests-linux.sh create mode 100644 cmd/geth/accountcmd_plugin.go create mode 100644 cmd/geth/accountcmd_plugin_test.go create mode 100644 cmd/geth/testdata/geth/nodekey create mode 100644 cmd/geth/testdata/geth/static-nodes.json create mode 100644 common/http/certificate.go create mode 100644 common/http/client.go create mode 100644 common/http/config.go create mode 100644 common/http/config_test.go create mode 100644 common/http/transport.go create mode 100644 common/slice.go create mode 100644 common/slice_test.go create mode 100644 consensus/istanbul/backend.go create mode 100644 consensus/istanbul/backend/api.go create mode 100644 consensus/istanbul/backend/backend.go create mode 100644 consensus/istanbul/backend/backend_test.go create mode 100644 consensus/istanbul/backend/engine.go create mode 100644 consensus/istanbul/backend/engine_test.go create mode 100644 consensus/istanbul/backend/handler.go create mode 100644 consensus/istanbul/backend/handler_test.go create mode 100644 consensus/istanbul/backend/snapshot.go create mode 100644 consensus/istanbul/backend/snapshot_test.go create mode 100644 consensus/istanbul/config.go create mode 100644 consensus/istanbul/core/backlog.go create mode 100644 consensus/istanbul/core/backlog_test.go create mode 100644 consensus/istanbul/core/commit.go create mode 100644 consensus/istanbul/core/commit_test.go create mode 100644 consensus/istanbul/core/core.go create mode 100644 consensus/istanbul/core/core_test.go create mode 100644 consensus/istanbul/core/errors.go create mode 100644 consensus/istanbul/core/events.go create mode 100644 consensus/istanbul/core/final_committed.go create mode 100644 consensus/istanbul/core/handler.go create mode 100644 consensus/istanbul/core/handler_test.go create mode 100644 consensus/istanbul/core/message_set.go create mode 100644 consensus/istanbul/core/message_set_test.go create mode 100644 consensus/istanbul/core/prepare.go create mode 100644 consensus/istanbul/core/prepare_test.go create mode 100644 consensus/istanbul/core/preprepare.go create mode 100644 consensus/istanbul/core/preprepare_test.go create mode 100644 consensus/istanbul/core/request.go create mode 100644 consensus/istanbul/core/request_test.go create mode 100644 consensus/istanbul/core/roundchange.go create mode 100644 consensus/istanbul/core/roundchange_test.go create mode 100644 consensus/istanbul/core/roundstate.go create mode 100644 consensus/istanbul/core/roundstate_test.go create mode 100644 consensus/istanbul/core/testbackend_test.go create mode 100644 consensus/istanbul/core/types.go create mode 100644 consensus/istanbul/core/types_test.go create mode 100644 consensus/istanbul/errors.go create mode 100644 consensus/istanbul/events.go create mode 100644 consensus/istanbul/types.go create mode 100644 consensus/istanbul/types_test.go create mode 100644 consensus/istanbul/utils.go create mode 100644 consensus/istanbul/validator.go create mode 100644 consensus/istanbul/validator/default.go create mode 100644 consensus/istanbul/validator/default_test.go create mode 100644 consensus/istanbul/validator/validator.go create mode 100644 consensus/protocol.go create mode 100644 containers/docker/develop-alpine/Dockerfile_BASE_30347 create mode 100644 containers/docker/develop-alpine/Dockerfile_LOCAL_30347 create mode 100644 containers/vagrant/.gitignore create mode 100755 containers/vagrant/provisioners/shell/centos.sh create mode 100755 containers/vagrant/provisioners/shell/debian.sh create mode 100755 containers/vagrant/provisioners/shell/ubuntu.sh create mode 100644 core/call_helper.go create mode 100644 core/constellation-test-keys/tm1.key create mode 100644 core/constellation-test-keys/tm1.pub create mode 100644 core/constellation-test-keys/tm1a.key create mode 100644 core/constellation-test-keys/tm1a.pub create mode 100644 core/dual_state_test.go create mode 100644 core/private_state_test.go create mode 100644 core/rawdb/database_quorum.go create mode 100644 core/rawdb/database_quorum_test.go create mode 100644 core/rawdb/freezer_test.go create mode 100644 core/state/account_extra_data.go create mode 100644 core/state/account_extra_data_test.go create mode 100644 core/state_transition_pmh.go create mode 100644 core/state_transition_pmh_test.go create mode 100644 core/state_transition_test.go create mode 100644 core/types/istanbul.go create mode 100644 core/types/istanbul_test.go create mode 100644 core/types/transaction_signing_private.go create mode 100644 core/types/transaction_signing_quorum_private_test.go create mode 100644 core/types/transaction_signing_quorum_test.go create mode 100644 core/vm/evm_test.go create mode 100644 core/vm/mock_interface.go create mode 100644 core/vm/runtime/evm_privacy_test.go create mode 100644 crypto/secp256k1/go.mod create mode 100644 docs/Quorum Design.png create mode 100644 docs/Quorum Whitepaper v0.2.pdf create mode 100644 docs/conf.py create mode 100644 docs/images/ContractDesign.png create mode 100644 docs/images/PermissionsModel.png create mode 100644 docs/images/logo-48x48.png create mode 100644 docs/images/logo.png create mode 100644 docs/theme/404.html create mode 100644 docs/theme/assets/javascripts/application.81068b3a.js create mode 100644 docs/theme/assets/stylesheets/application.668e8dde.css create mode 100644 docs/theme/assets/stylesheets/extra.css create mode 100644 docs/theme/base.html create mode 100644 docs/theme/partials/footer.html create mode 100644 docs/theme/partials/header.html create mode 100644 docs/theme/partials/nav.html create mode 100644 eth/config_test.go create mode 100644 eth/quorum_protocol.go create mode 100644 ethclient/privateTransactionManagerClient.go create mode 100644 ethclient/privateTransactionManagerClient_test.go create mode 100644 extension/api.go create mode 100644 extension/backend.go create mode 100644 extension/backend_test.go create mode 100644 extension/client.go create mode 100644 extension/contract_facade.go create mode 100644 extension/data_handler.go create mode 100644 extension/data_handler_test.go create mode 100644 extension/extensionContracts/contract_extender.go create mode 100644 extension/extensionContracts/contract_extender.sol create mode 100644 extension/extensionContracts/extensionHandler.go create mode 100644 extension/extensionContracts/types.go create mode 100644 extension/extension_utilities.go create mode 100644 extension/privacyExtension/state_set_utilities.go create mode 100644 extension/privacyExtension/state_set_utilities_test.go create mode 100644 extension/privacyExtension/state_setter.go create mode 100644 extension/services_factory.go create mode 100644 extension/state_fetcher.go create mode 100644 extension/state_fetcher_test.go create mode 100644 extension/types.go create mode 100644 internal/build/gosrc.go create mode 100644 internal/build/util_test.go create mode 100644 internal/ethapi/api_test.go create mode 100644 internal/plugin/protocol.go create mode 100644 log/emit_checkpoint.go create mode 100644 logo.png create mode 100644 mkdocs.yml create mode 100644 multitenancy/authorization_provider.go create mode 100644 multitenancy/authorization_provider_test.go create mode 100644 multitenancy/types.go create mode 100644 params/network_params_test.go create mode 100644 params/protocol_params_test.go create mode 100644 params/quorum.go create mode 100644 permission/api.go create mode 100644 permission/backend.go create mode 100644 permission/connection.go create mode 100644 permission/core/cache.go create mode 100644 permission/core/cache_test.go create mode 100644 permission/core/permissions.go create mode 100644 permission/core/permissions_test.go create mode 100644 permission/core/types/backend.go create mode 100644 permission/core/types/contract.go create mode 100644 permission/permission.go create mode 100644 permission/permission_test.go create mode 100644 permission/v1/backend.go create mode 100644 permission/v1/bind/accounts.go create mode 100644 permission/v1/bind/nodes.go create mode 100644 permission/v1/bind/org.go create mode 100644 permission/v1/bind/permission_impl.go create mode 100644 permission/v1/bind/permission_interface.go create mode 100644 permission/v1/bind/permission_upgr.go create mode 100644 permission/v1/bind/roles.go create mode 100644 permission/v1/bind/voter.go create mode 100644 permission/v1/contract.go create mode 100644 permission/v1/contract/AccountManager.sol create mode 100644 permission/v1/contract/NodeManager.sol create mode 100644 permission/v1/contract/OrgManager.sol create mode 100644 permission/v1/contract/PermissionsImplementation.sol create mode 100644 permission/v1/contract/PermissionsInterface.sol create mode 100644 permission/v1/contract/PermissionsUpgradable.sol create mode 100644 permission/v1/contract/RoleManager.sol create mode 100644 permission/v1/contract/VoterManager.sol create mode 100644 permission/v1/contract/gen/AccountManager.abi create mode 100644 permission/v1/contract/gen/AccountManager.bin create mode 100644 permission/v1/contract/gen/NodeManager.abi create mode 100644 permission/v1/contract/gen/NodeManager.bin create mode 100644 permission/v1/contract/gen/OrgManager.abi create mode 100644 permission/v1/contract/gen/OrgManager.bin create mode 100644 permission/v1/contract/gen/PermissionsImplementation.abi create mode 100644 permission/v1/contract/gen/PermissionsImplementation.bin create mode 100644 permission/v1/contract/gen/PermissionsInterface.abi create mode 100644 permission/v1/contract/gen/PermissionsInterface.bin create mode 100644 permission/v1/contract/gen/PermissionsUpgradable.abi create mode 100644 permission/v1/contract/gen/PermissionsUpgradable.bin create mode 100644 permission/v1/contract/gen/RoleManager.abi create mode 100644 permission/v1/contract/gen/RoleManager.bin create mode 100644 permission/v1/contract/gen/VoterManager.abi create mode 100644 permission/v1/contract/gen/VoterManager.bin create mode 100644 permission/v1/contract/gen/gen.go create mode 100644 permission/v2/backend.go create mode 100644 permission/v2/bind/accounts.go create mode 100644 permission/v2/bind/nodes.go create mode 100644 permission/v2/bind/org.go create mode 100644 permission/v2/bind/permission_impl.go create mode 100644 permission/v2/bind/permission_interface.go create mode 100644 permission/v2/bind/permission_upgr.go create mode 100644 permission/v2/bind/roles.go create mode 100644 permission/v2/bind/voter.go create mode 100644 permission/v2/contract.go create mode 100644 permission/v2/contract/AccountManager.sol create mode 100644 permission/v2/contract/NodeManager.sol create mode 100644 permission/v2/contract/OrgManager.sol create mode 100644 permission/v2/contract/PermissionsImplementation.sol create mode 100644 permission/v2/contract/PermissionsInterface.sol create mode 100644 permission/v2/contract/PermissionsUpgradable.sol create mode 100644 permission/v2/contract/RoleManager.sol create mode 100644 permission/v2/contract/VoterManager.sol create mode 100644 permission/v2/contract/gen/AccountManager.abi create mode 100644 permission/v2/contract/gen/AccountManager.bin create mode 100644 permission/v2/contract/gen/NodeManager.abi create mode 100644 permission/v2/contract/gen/NodeManager.bin create mode 100644 permission/v2/contract/gen/OrgManager.abi create mode 100644 permission/v2/contract/gen/OrgManager.bin create mode 100644 permission/v2/contract/gen/PermissionsImplementation.abi create mode 100644 permission/v2/contract/gen/PermissionsImplementation.bin create mode 100644 permission/v2/contract/gen/PermissionsInterface.abi create mode 100644 permission/v2/contract/gen/PermissionsInterface.bin create mode 100644 permission/v2/contract/gen/PermissionsUpgradable.abi create mode 100644 permission/v2/contract/gen/PermissionsUpgradable.bin create mode 100644 permission/v2/contract/gen/RoleManager.abi create mode 100644 permission/v2/contract/gen/RoleManager.bin create mode 100644 permission/v2/contract/gen/VoterManager.abi create mode 100644 permission/v2/contract/gen/VoterManager.bin create mode 100644 permission/v2/contract/gen/gen.go create mode 100644 plugin/account/connector.go create mode 100644 plugin/account/creator.go create mode 100644 plugin/account/gateway.go create mode 100644 plugin/account/gateway_test.go create mode 100644 plugin/account/internal/testutils/matchers.go create mode 100644 plugin/account/reloadableimpl.go create mode 100644 plugin/account/service.go create mode 100644 plugin/api.go create mode 100644 plugin/base.go create mode 100644 plugin/central.go create mode 100644 plugin/central_test.go create mode 100644 plugin/downloader.go create mode 100644 plugin/downloader_test.go create mode 100644 plugin/gen/docs.markdown.tmpl create mode 100644 plugin/gen/gen.go create mode 100644 plugin/gen/proto_common/init.pb.go create mode 100644 plugin/gen/proto_common/mock_init.go create mode 100644 plugin/helloworld/connector.go create mode 100644 plugin/helloworld/gateway.go create mode 100644 plugin/helloworld/gateway_test.go create mode 100644 plugin/helloworld/service.go create mode 100644 plugin/initializer/connector.go create mode 100644 plugin/initializer/gateway.go create mode 100644 plugin/initializer/gateway_test.go create mode 100644 plugin/initializer/service.go create mode 100644 plugin/local_verifier.go create mode 100644 plugin/local_verifier_test.go create mode 100644 plugin/online_verifier.go create mode 100644 plugin/plugin_templates.go create mode 100644 plugin/security/connector.go create mode 100644 plugin/security/gateway.go create mode 100644 plugin/security/gateway_test.go create mode 100644 plugin/security/service.go create mode 100644 plugin/security/utils.go create mode 100644 plugin/service.go create mode 100644 plugin/service_test.go create mode 100644 plugin/settings.go create mode 100644 plugin/settings_test.go create mode 100644 plugin/utils.go create mode 100644 plugin/utils_test.go create mode 100644 plugin/verifier.go create mode 100644 plugin/verifier_test.go create mode 100644 private/cache/cache.go create mode 100644 private/engine/common.go create mode 100644 private/engine/common_test.go create mode 100644 private/engine/constellation/constellation.go create mode 100644 private/engine/constellation/node.go create mode 100644 private/engine/notinuse/notInUsePrivateTxManager.go create mode 100644 private/engine/notinuse/notInUsePrivateTxManager_test.go create mode 100644 private/engine/tessera/model.go create mode 100644 private/engine/tessera/tessera.go create mode 100644 private/engine/tessera/tessera_test.go create mode 100644 private/engine/tessera/tessera_version_checker.go create mode 100644 private/engine/tessera/tessera_version_checker_test.go create mode 100644 private/engine/tessera/tessera_version_reader.go create mode 100644 private/engine/tessera/tessera_version_reader_test.go create mode 100644 private/private.go create mode 100644 private/private_test.go create mode 100755 raft/api.go create mode 100644 raft/backend.go create mode 100644 raft/constants.go create mode 100644 raft/events.go create mode 100644 raft/handler.go create mode 100644 raft/handler_test.go create mode 100644 raft/listener.go create mode 100644 raft/minter.go create mode 100644 raft/minter_test.go create mode 100644 raft/peer.go create mode 100644 raft/persistence.go create mode 100644 raft/snapshot.go create mode 100644 raft/speculative_chain.go create mode 100644 raft/util.go create mode 100644 raft/wal.go create mode 100644 rpc/security.go create mode 100644 rpc/security_test.go create mode 100644 signer/core/plugin_api.go diff --git a/.bintray.json b/.bintray.json new file mode 100644 index 0000000000..953647582c --- /dev/null +++ b/.bintray.json @@ -0,0 +1,37 @@ +{ + "package": { + "name": "geth", + "repo": "quorum", + "subject": "_ORGANIZATION_", + "vcs_url": "https://github.com/jpmorganchase/quorum", + "licenses": [ + "LGPL-3.0" + ] + }, + "version": { + "name": "_TRAVIS_TAG_", + "desc": "Quorum: _TRAVIS_TAG_, Geth: _GETH_VERSION_, Commit: _TRAVIS_COMMIT_, Build Number: _TRAVIS_BUILD_NUMBER_", + "released": "_RELEASED_DATE_", + "vcs_tag": "_TRAVIS_TAG_", + "gpgSign": true, + "attributes": [ + { + "name": "Travis", + "values": [ + "_TRAVIS_JOB_WEB_URL_" + ], + "type": "string" + } + ] + }, + "files": [ + { + "includePattern": "/dist/(.*.tar.gz)", + "uploadPattern": "_TRAVIS_TAG_/$1", + "matrixParams": { + "override": 1 + } + } + ], + "publish": true +} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f87996cdcb..ab2def41c7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,25 +1,21 @@ # Contributing -Thank you for considering to help out with the source code! We welcome -contributions from anyone on the internet, and are grateful for even the +Thank you for your interest in contributing to Quorum! +We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes! -If you'd like to contribute to go-ethereum, please fork, fix, commit and send a -pull request for the maintainers to review and merge into the main code base. If -you wish to submit more complex changes though, please check up with the core -devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) to -ensure those changes are in line with the general philosophy of the project -and/or get some early feedback which can make both your efforts much lighter as -well as our review and merge procedures quick and simple. +If you'd like to contribute to quorum please fork, fix, commit and +send a pull request. Commits which do not comply with the coding standards +are ignored. ## Coding guidelines Please make sure your contributions adhere to our coding guidelines: - * Code must adhere to the official Go -[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines + * Code must adhere to the official Go +[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). - * Code must be documented adhering to the official Go + * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. * Pull requests need to be based on and opened against the `master` branch. * Commit messages should be prefixed with the package(s) they modify. @@ -27,10 +23,10 @@ Please make sure your contributions adhere to our coding guidelines: ## Can I have feature X -Before you submit a feature request, please check and make sure that it isn't -possible through some other means. The JavaScript-enabled console is a powerful -feature in the right hands. Please check our -[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info +Before you submit a feature request, please check and make sure that it isn't +possible through some other means. The JavaScript-enabled console is a powerful +feature in the right hands. Please check our +[developer documentation](https://docs.goquorum.consensys.net/en/latest/) for more info and help. ## Configuration, dependencies, and tests diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 59285e456d..70903dee4f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -7,8 +7,10 @@ For general questions please use [discord](https://discord.gg/nthXNEv) or the Et #### System information Geth version: `geth version` + OS & Version: Windows/Linux/OSX -Commit hash : (if `develop`) + +Branch, Commit Hash or Release: `git status` #### Expected behaviour diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..41d58f8a7a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,273 @@ +name: Build Check +on: + push: + paths-ignore: + - 'docs/**' + - '**.md' + - 'mkdocs.yml' + - '.gitignore' + branches: + - master +env: + GO_VERSION: 1.15.5 + GOPATH: ${{ github.workspace }}/go + WORKING_DIR: ${{ github.workspace }}/go/src/github.com/ethereum/go-ethereum +jobs: + build: + name: 'Run tests and build on ${{ matrix.os }}' + strategy: + fail-fast: false + matrix: + # Not enable for macos as there's a consistent failure: + # --- FAIL: TestUPNP_DDWRT (2.20s) + # ###[error] natupnp_test.go:165: not discovered + # must be sommething with Github Actions VM networking setup. + # Event Ubuntu requires a workaround + os: [ "ubuntu-18.04" ] + env: + QUORUM_IGNORE_TEST_PACKAGES: github.com/ethereum/go-ethereum/les,github.com/ethereum/go-ethereum/les/flowcontrol,github.com/ethereum/go-ethereum/mobile + runs-on: ${{ matrix.os }} + steps: + - name: 'Setup Go ${{ env.GO_VERSION }}' + uses: actions/setup-go@v1 + with: + go-version: ${{ env.GO_VERSION }} + - name: 'Check out project files' + uses: actions/checkout@v2 + with: + submodules: recursive + path: ${{ env.WORKING_DIR }} + - name: 'Apply workaround to fix networking in Linux' + if: runner.os == 'Linux' + run: | + # https://github.com/actions/virtual-environments/issues/798 + sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf + - name: 'Prepare environment' + run: | + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + - name: 'Run tests and build all' + working-directory: ${{ env.WORKING_DIR }} + run: | + make test all + publish-docker: + name: Publish Docker Image + needs: + - build + runs-on: ubuntu-18.04 + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + - name: 'Build and publish to Docker Hub' + uses: docker/build-push-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + repository: ${{ secrets.DOCKER_REPO }} + tag_with_ref: true + add_git_labels: true + notify: + if: always() + name: Notify + needs: + - build + - publish-docker + runs-on: ubuntu-18.04 + steps: + - name: 'Setup metadata' + id: setup + run: | + gitref_path="${{ github.ref }}" + gitref_path=${gitref_path/refs\/heads/tree} # for refs/heads/my-branch + gitref_path=${gitref_path/refs\/tags/tree} # for refs/tags/v1.0.0 + gitref_path=${gitref_path#refs\/} # for refs/pull/123/merge + gitref_path=${gitref_path%/merge} # for refs/pull/123/merge + echo "::set-output name=gitref-path::$gitref_path" + - name: 'Prepare Slack message with full info' + id: status + uses: actions/github-script@0.8.0 + with: + script: | + var gitref_path = "${{ steps.setup.outputs.gitref-path }}" + //////////////////////////////////// + // retrieve workflow run data + //////////////////////////////////// + console.log("get workflow run") + const wf_run = await github.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.run_id }} + }) + console.log(wf_run.data) + console.log("get jobs for workflow run:", wf_run.data.jobs_url) + const jobs_response = await github.request(wf_run.data.jobs_url) + //////////////////////////////////// + // build slack notification message + //////////////////////////////////// + // some utility functions + var date_diff_func = function(start, end) { + var duration = end - start + // format the duration + var delta = duration / 1000 + var days = Math.floor(delta / 86400) + delta -= days * 86400 + var hours = Math.floor(delta / 3600) % 24 + delta -= hours * 3600 + var minutes = Math.floor(delta / 60) % 60 + delta -= minutes * 60 + var seconds = Math.floor(delta % 60) + var format_func = function(v, text, check) { + if (v <= 0 && check) { + return "" + } else { + return v + text + } + } + return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false) + } + var status_icon_func = function(s) { + switch (s) { + case "w_success": + return ":white_check_mark:" + case "w_failure": + return ":no_entry:" + case "w_cancelled": + return ":warning:" + case "success": + return "\u2713" + case "failure": + return "\u2717" + default: + return "\u20e0" + } + } + // build the message + var job_blocks = [] + var is_wf_success = true + var is_wf_failure = false + for (j of jobs_response.data.jobs) { + console.log(j.name, ":", j.status, j.conclusion, j.started_at, j.completed_at) + // ignore the current job running this script + if (j.status != "completed") { + continue + } + if (j.conclusion != "success") { + is_wf_success = false + } + if (j.conclusion == "failure") { + is_wf_failure = true + } + job_blocks.push({ + type: "section", + text: { + type: "mrkdwn", + text: `${status_icon_func(j.conclusion)} <${j.html_url}|${j.name}> took ${date_diff_func(new Date(j.started_at), new Date(j.completed_at))}` + } + }) + } + var workflow_status = "w_cancelled" + if (is_wf_success) { + workflow_status = "w_success" + } else if (is_wf_failure) { + workflow_status = "w_failure" + } + var context_elements = [ + { + "type": "mrkdwn", + "text": "*Repo:* " + }, + { + "type": "mrkdwn", + "text": `*Branch:* ` + }, + { + "type": "mrkdwn", + "text": `*Event:* ${wf_run.data.event}` + }, + { + "type": "mrkdwn", + "text": `*Commit:* ` + }, + { + "type": "mrkdwn", + "text": `*Author:* ${wf_run.data.head_commit.author.name}` + } + ] + var header_blocks = [ + { + type: "section", + text: { + type: "mrkdwn", + text: `${status_icon_func(workflow_status)} *${{ github.workflow }}* <${wf_run.data.html_url}|#${{ github.run_number }}> took ${date_diff_func(new Date(wf_run.data.created_at), new Date(wf_run.data.updated_at))}` + } + }, + { + type: "context", + elements: context_elements, + }, + { + type: "divider" + } + ] + var slack_msg = { + blocks: [].concat(header_blocks, job_blocks) + } + return slack_msg + - name: 'Prepare Slack message with partial info' + id: short_status + if: failure() + uses: actions/github-script@0.8.0 + with: + script: | + //////////////////////////////////// + // retrieve workflow run data + //////////////////////////////////// + const wf_run = await github.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.run_id }} + }) + var date_diff_func = function(start, end) { + var duration = end - start + // format the duration + var delta = duration / 1000 + var days = Math.floor(delta / 86400) + delta -= days * 86400 + var hours = Math.floor(delta / 3600) % 24 + delta -= hours * 3600 + var minutes = Math.floor(delta / 60) % 60 + delta -= minutes * 60 + var seconds = Math.floor(delta % 60) + var format_func = function(v, text, check) { + if (v <= 0 && check) { + return "" + } else { + return v + text + } + } + return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false) + } + var slack_msg = { + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: `:skull_and_crossbones: *${{ github.workflow }}* <${wf_run.data.html_url}|#${{ github.run_number }}> (took ${date_diff_func(new Date(wf_run.data.created_at), new Date(wf_run.data.updated_at))})` + } + } + ] + } + return slack_msg + - name: 'Send to Slack' + if: always() + run: | + cat < long_message.json + ${{ steps.status.outputs.result }} + JSON + cat < short_message.json + ${{ steps.short_status.outputs.result }} + JSON + _post() { + curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} -H "Content-type: application/json" --data "@${1}" + } + _post "long_message.json" || _post "short_message.json" \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..5b07cbbcd6 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,116 @@ +name: Pull Request Check +on: + pull_request: + paths-ignore: + - 'docs/**' + - '**.md' + - .gitignore +env: + GO_VERSION: 1.15.5 +jobs: + lint: + name: 'Code linters' + runs-on: ubuntu-18.04 + steps: + - name: 'Setup Go ${{ env.GO_VERSION }}' + uses: actions/setup-go@v1 + with: + go-version: ${{ env.GO_VERSION }} + - name: 'Check out project files' + uses: actions/checkout@v2 + with: + submodules: false + - name: 'Prepare environment' + run: | + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + - name: 'Run code linters' + run: | + GO111MODULE=on make lint + unit-tests: + name: 'Unit tests in ${{ matrix.os }}' + strategy: + fail-fast: false + matrix: + os: ["ubuntu-18.04"] + env: + QUORUM_IGNORE_TEST_PACKAGES: github.com/ethereum/go-ethereum/les,github.com/ethereum/go-ethereum/les/flowcontrol,github.com/ethereum/go-ethereum/mobile + runs-on: ${{ matrix.os }} + steps: + - name: 'Setup Go ${{ env.GO_VERSION }}' + uses: actions/setup-go@v1 + with: + go-version: ${{ env.GO_VERSION }} + - name: 'Check out project files' + uses: actions/checkout@v2 + with: + submodules: recursive + - name: 'Prepare environment' + run: | + # https://github.com/actions/virtual-environments/issues/798 + sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf + + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + - name: 'Run unit tests' + run: | + make test + docker-build: + name: 'Build Docker image' + runs-on: ubuntu-18.04 + steps: + - name: 'Check out project files' + uses: actions/checkout@v2 + - name: 'Build docker image' + id: build + run: | + output_dir=${{ runner.temp }}/docker + mkdir -p $output_dir + docker build -t quorumengineering/quorum:pr . + docker save quorumengineering/quorum:pr > quorum-pr.tar + tar cfvz $output_dir/quorum-pr.tar.gz quorum-pr.tar + echo "::set-output name=output_dir::$output_dir" + - name: 'Upload workflow artifact - Docker image' + uses: actions/upload-artifact@v1 + with: + name: docker-image + path: ${{ steps.build.outputs.output_dir }} + acceptance-tests: + name: Acceptance tests (${{ matrix.tag }}) + needs: + - docker-build + if: success() + strategy: + fail-fast: false + matrix: + # list of tag expression being executed in parallel + # for PR, only selective tests are run. + # More comprehensive suites are scheduled to run in master + tag: + - 'basic || basic-raft || (advanced && raft) || networks/typical::raft' + - 'basic || basic-istanbul || (advanced && istanbul && !block-heights) || networks/typical::istanbul' + runs-on: ubuntu-18.04 + steps: + - name: 'Download workflow artifact - Docker image' + uses: actions/download-artifact@v1 + with: + name: docker-image + - name: 'Load Docker image' + id: setup + run: | + tar xfvz docker-image/quorum-pr.tar.gz + docker load --input quorum-pr.tar + echo "::set-output name=outputDir::${{ runner.temp }}" + - name: 'Run acceptance tests' + run: | + docker run --rm \ + --network host \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${{ steps.setup.outputs.outputDir }}:${{ steps.setup.outputs.outputDir }} \ + -e TF_VAR_quorum_docker_image='{ name = "quorumengineering/quorum:pr", local = true }' \ + quorumengineering/acctests:latest test \ + -Pauto \ + -Dauto.outputDir=${{ steps.setup.outputs.outputDir }} \ + -Dtags="${{ matrix.tag }}" + - name: 'Debug' + run: | + docker images + docker ps -a \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..310c6940b7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,365 @@ +name: Release +on: + push: + tags: + - 'v*' +env: + GO_VERSION: 1.15.5 + GOPATH: ${{ github.workspace }}/go + WORKING_DIR: ${{ github.workspace }}/go/src/github.com/ethereum/go-ethereum +jobs: + publish-docker: + name: Publish Docker Image + runs-on: ubuntu-18.04 + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + - name: 'Extract Docker Image Tag' + id: extract + run: | + REF=${{ github.ref }} + echo ::set-output name=image_tag::$(echo $REF | sed 's/refs\/tags\/v//g') + echo ::set-output name=image_tag_minor_latest::$(echo $REF | sed -e 's/refs\/tags\/v//g' -e 's/^\([[:digit:]]*\.[[:digit:]]*\).*/\1/') + - name: 'Build and publish to Docker Hub' + uses: docker/build-push-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + repository: ${{ secrets.DOCKER_REPO }} + tags: ${{ steps.extract.outputs.image_tag }}, ${{ steps.extract.outputs.image_tag_minor_latest }} + add_git_labels: true + build: + name: 'Build binary for ${{ matrix.os }}' + strategy: + fail-fast: false + matrix: + os: ["ubuntu-18.04", "macos-latest"] + runs-on: ${{ matrix.os }} + steps: + - name: 'Setup Go ${{ env.GO_VERSION }}' + uses: actions/setup-go@v1 + with: + go-version: ${{ env.GO_VERSION }} + - name: 'Prepare environment' + id: env + run: | + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + echo "::set-output name=key::$(go env GOOS)_$(go env GOARCH)" + echo "::set-output name=version::${GITHUB_REF##*/}" + - name: 'Check out project files' + uses: actions/checkout@v2 + with: + submodules: recursive + path: ${{ env.WORKING_DIR }} + - name: 'Build geth' + working-directory: ${{ env.WORKING_DIR }} + run: | + make geth + mkdir -p build/artifact + tar cfvz build/artifact/geth_${{ steps.env.outputs.version }}_${{ steps.env.outputs.key }}.tar.gz -C build/bin geth + - name: 'Upload artifact' + uses: actions/upload-artifact@v2 + with: + path: ${{ env.WORKING_DIR }}/build/artifact + name: ${{ steps.env.outputs.key }} + prepare-bintray: + name: 'Prepare Bintray' + runs-on: ubuntu-18.04 + steps: + - name: 'Setup jfrog CLI' + uses: jfrog/setup-jfrog-cli@v1 + - name: 'Create new version in Bintray' + id: prepare_bintray + run: | + TAG="${GITHUB_REF##*/}" + PACKAGE="${{ secrets.BINTRAY_PACKAGE }}" # e.g.: quorumengineering/quorum/geth + VERSION="$PACKAGE/$TAG" + jfrog bt package-show --key ${{ secrets.BINTRAY_API_KEY }} --user ${{ secrets.BINTRAY_USER }} $PACKAGE + echo "Checking $VERSION" + jfrog bt version-show --key ${{ secrets.BINTRAY_API_KEY }} --user ${{ secrets.BINTRAY_USER }} $VERSION && x=0 || x=1 + if [ $x -eq 0 ]; then + echo "$VERSION already exists" + else + jfrog bt version-create --key ${{ secrets.BINTRAY_API_KEY }} --user ${{ secrets.BINTRAY_USER }} \ + --vcs-tag $TAG --released $(date -u +"%Y-%m-%dT%H:%M:%SZ") \ + $VERSION + fi + deploy-bintray: + name: 'Deploy binaries to Bintray' + needs: + - build + - prepare-bintray + runs-on: ubuntu-18.04 + steps: + - name: 'Setup jfrog CLI' + uses: jfrog/setup-jfrog-cli@v1 + - name: 'Download artifacts' + uses: actions/download-artifact@v2 + with: + path: artifact + - name: 'Upload artifacts to Bintray' + run: | + TAG="${GITHUB_REF##*/}" + jfrog bt upload --key ${{ secrets.BINTRAY_API_KEY }} --user ${{ secrets.BINTRAY_USER }} --publish --override \ + "artifact/*/*.*" \ + ${{ secrets.BINTRAY_PACKAGE }}/$TAG \ + $TAG/ + draft-release: + name: 'Draft Github release' + needs: + - deploy-bintray + - publish-docker + runs-on: ubuntu-18.04 + steps: + - name: 'Check out project files' + uses: actions/checkout@v2 + - name: 'Generate release notes' + id: release_notes + run: | + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + package="${{ secrets.BINTRAY_PACKAGE }}" + repo="${package%/*}" + file="generated-release-notes.md" + current_version="${GITHUB_REF##*/}" + last_version=$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`) + last_release_date=$(git log -1 --format=%cd --date=short $last_version) + echo "Last version: $last_version on $last_release_date" + # pulling from git logs + curl -q -s -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/search/issues?q=repo:ConsenSys/quorum+is:pr+is:merged+merged%3A>=$last_release_date+sort%3Aupdated-desc" | jq -r '"* " + (.items[]|.title + " #" + (.number|tostring))' \ + >> $file + # pulling file hashes from Bintray + echo "" >> $file + echo "| Filename | SHA256 Hash |" >> $file + echo "|:---------|:------------|" >> $file + curl -q -s -u ${{ secrets.BINTRAY_USER }}:${{ secrets.BINTRAY_API_KEY}} "https://api.bintray.com/packages/$package/versions/$current_version/files" \ + | jq '.[] | select(.name | endswith(".asc") | not) | "|[\(.name)](https://bintray.com/'"$repo"'/download_file?file_path=\(.path))|`\(.sha256)`|"' -r \ + >> $file + content=$(cat $file) + # escape newline + content="${content//'%'/'%25'}" + content="${content//$'\n'/'%0A'}" + content="${content//$'\r'/'%0D'}" + echo "::set-output name=content::$content" + - name: 'Create Github draft release' + uses: actions/create-release@v1 + env: + # This token is provided by Actions, you do not need to create your own token + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + body: | + ${{ steps.release_notes.outputs.content }} + draft: true + prerelease: false + notify: + if: always() + name: 'Notify' + needs: + - build + - deploy-bintray + - publish-docker + - draft-release + runs-on: ubuntu-18.04 + steps: + - name: 'Setup metadata' + id: setup + run: | + gitref_path="${{ github.ref }}" + gitref_path=${gitref_path/refs\/heads/tree} # for refs/heads/my-branch + gitref_path=${gitref_path/refs\/tags/tree} # for refs/tags/v1.0.0 + gitref_path=${gitref_path#refs\/} # for refs/pull/123/merge + gitref_path=${gitref_path%/merge} # for refs/pull/123/merge + echo "::set-output name=gitref-path::$gitref_path" + echo "::set-output name=version::${GITHUB_REF##*/}" + - name: 'Prepare Slack message with full info' + id: status + uses: actions/github-script@0.8.0 + with: + script: | + var gitref_path = "${{ steps.setup.outputs.gitref-path }}" + //////////////////////////////////// + // retrieve workflow run data + //////////////////////////////////// + console.log("get workflow run") + const wf_run = await github.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.run_id }} + }) + console.log(wf_run.data) + console.log("get jobs for workflow run:", wf_run.data.jobs_url) + const jobs_response = await github.request(wf_run.data.jobs_url) + //////////////////////////////////// + // build slack notification message + //////////////////////////////////// + // some utility functions + var date_diff_func = function(start, end) { + var duration = end - start + // format the duration + var delta = duration / 1000 + var days = Math.floor(delta / 86400) + delta -= days * 86400 + var hours = Math.floor(delta / 3600) % 24 + delta -= hours * 3600 + var minutes = Math.floor(delta / 60) % 60 + delta -= minutes * 60 + var seconds = Math.floor(delta % 60) + var format_func = function(v, text, check) { + if (v <= 0 && check) { + return "" + } else { + return v + text + } + } + return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false) + } + var status_icon_func = function(s) { + switch (s) { + case "w_success": + return ":white_check_mark:" + case "w_failure": + return ":no_entry:" + case "w_cancelled": + return ":warning:" + case "success": + return "\u2713" + case "failure": + return "\u2717" + default: + return "\u20e0" + } + } + // build the message + var job_blocks = [] + var is_wf_success = true + var is_wf_failure = false + for (j of jobs_response.data.jobs) { + console.log(j.name, ":", j.status, j.conclusion, j.started_at, j.completed_at) + // ignore the current job running this script + if (j.status != "completed") { + continue + } + if (j.conclusion != "success") { + is_wf_success = false + } + if (j.conclusion == "failure") { + is_wf_failure = true + } + job_blocks.push({ + type: "section", + text: { + type: "mrkdwn", + text: `${status_icon_func(j.conclusion)} <${j.html_url}|${j.name}> took ${date_diff_func(new Date(j.started_at), new Date(j.completed_at))}` + } + }) + } + var workflow_status = "w_cancelled" + if (is_wf_success) { + workflow_status = "w_success" + } else if (is_wf_failure) { + workflow_status = "w_failure" + } + var context_elements = [ + { + "type": "mrkdwn", + "text": "*Repo:* " + }, + { + "type": "mrkdwn", + "text": `*Branch:* ` + }, + { + "type": "mrkdwn", + "text": `*Event:* ${wf_run.data.event}` + }, + { + "type": "mrkdwn", + "text": `*Commit:* ` + }, + { + "type": "mrkdwn", + "text": `*Author:* ${wf_run.data.head_commit.author.name}` + } + ] + var header_blocks = [ + { + type: "section", + text: { + type: "mrkdwn", + text: `${status_icon_func(workflow_status)} *${{ github.workflow }} ${{ steps.setup.outputs.version }}* <${wf_run.data.html_url}|#${{ github.run_number }}> took ${date_diff_func(new Date(wf_run.data.created_at), new Date(wf_run.data.updated_at))}` + } + }, + { + type: "context", + elements: context_elements, + }, + { + type: "divider" + } + ] + var slack_msg = { + blocks: [].concat(header_blocks, job_blocks) + } + return slack_msg + - name: 'Prepare Slack message with partial info' + id: short_status + if: failure() + uses: actions/github-script@0.8.0 + with: + script: | + //////////////////////////////////// + // retrieve workflow run data + //////////////////////////////////// + const wf_run = await github.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.run_id }} + }) + var date_diff_func = function(start, end) { + var duration = end - start + // format the duration + var delta = duration / 1000 + var days = Math.floor(delta / 86400) + delta -= days * 86400 + var hours = Math.floor(delta / 3600) % 24 + delta -= hours * 3600 + var minutes = Math.floor(delta / 60) % 60 + delta -= minutes * 60 + var seconds = Math.floor(delta % 60) + var format_func = function(v, text, check) { + if (v <= 0 && check) { + return "" + } else { + return v + text + } + } + return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false) + } + var slack_msg = { + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: `:skull_and_crossbones: *${{ github.workflow }}* <${wf_run.data.html_url}|#${{ github.run_number }}> (took ${date_diff_func(new Date(wf_run.data.created_at), new Date(wf_run.data.updated_at))})` + } + } + ] + } + return slack_msg + - name: 'Send to Slack' + if: always() + run: | + cat < long_message.json + ${{ steps.status.outputs.result }} + JSON + cat < short_message.json + ${{ steps.short_status.outputs.result }} + JSON + _post() { + curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} -H "Content-type: application/json" --data "@${1}" + } + _post "long_message.json" || _post "short_message.json" + diff --git a/.gitignore b/.gitignore index 1ee8b83022..f1fcc40fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ */**/*tx_database* */**/*dapps* build/_vendor/pkg +.idea +*.iml #* .#* @@ -47,3 +49,6 @@ profile.cov /dashboard/assets/package-lock.json **/yarn-error.log + +# QUORUM +generated-release-notes.md \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 18b325e206..395a91fe1b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,7 @@ # This file configures github.com/golangci/golangci-lint. run: - timeout: 3m + timeout: 5m tests: true # default is true. Enables skipping of directories: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000000..56c6ae7526 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,33 @@ + +# Building Quorum + +Note: Building Quorum requires both a Go (version 1.9 or later) and a C compiler. You can install them using your favourite package manager. + +Clone the repository and build the source: + +``` +git clone https://github.com/jpmorganchase/quorum.git +cd quorum +make all +make test +``` + +Binaries are placed within `./build/bin`, most notably `geth` and `bootnode`. Either add this directory to your `$PATH` or copy those two bins into your PATH: + +```sh +# assumes that /usr/local/bin is in your PATH +cp ./build/bin/geth ./build/bin/bootnode /usr/local/bin/ +``` + +# Building on Windows +It is possible to build and run Quorum on Windows. Below are the steps required, please use Slack for any questions or support. Keep in mind that original Go-Ethereum provides a number of helper scripts for environment configuration and build execution. We're not planning ot provide this ourselves, but steps below explain what you may need to set up on your system to create such scripts. + +1. Install Go version 1.10 or 1.11 for Windows +2. Create a folder that you will bind to be GOPATH + * Set this folder as GOPATH in your environment: `set GOPATH=your_new_folder` + * Go will build binaries to a sub-path of GOPATH, so add it into PATH: `set "PATH=%PATH%;%GOPATH%\bin\"` +3. In GOPATH folder, create `src\github.com\ethereum` set of sub paths +4. Checkout Quorum into newly created GOPATH structure like so: `git clone https://github.com/jpmorganchase/quorum.git GOPATH\src\github.com\ethereum\go-ethereum`. Note: **go-ethereum** added at the end is required since Quorum occupies the same namespace as go-ethereum project. For this reason, on windows, a separate GOPATH is recommended if you need to build both projects +5. Change directory into checked out project and build it with `go install -v ./cmd/geth` + +The steps mimic the documentation from [go-ethereum project](https://github.com/ethereum/go-ethereum/wiki/Installation-instructions-for-Windows), please reference as needed diff --git a/Dockerfile b/Dockerfile index 54453c4df5..da99963d00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,17 @@ # Build Geth in a stock Go builder container -FROM golang:1.14-alpine as builder +FROM golang:1.15.5-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git ADD . /go-ethereum -RUN cd /go-ethereum && make geth +RUN cd /go-ethereum && make geth bootnode # Pull Geth into a second stage deploy alpine container FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ +COPY --from=builder /go-ethereum/build/bin/bootnode /usr/local/bin/ EXPOSE 8545 8546 8547 30303 30303/udp ENTRYPOINT ["geth"] diff --git a/Makefile b/Makefile index 67095f4d00..0cac496449 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,11 @@ geth: @echo "Done building." @echo "Run \"$(GOBIN)/geth\" to launch geth." +bootnode: + $(GORUN) build/ci.go install ./cmd/bootnode + @echo "Done building." + @echo "Run \"$(GOBIN)/bootnode\" to launch bootnode." + all: $(GORUN) build/ci.go install diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000000..e38ff02969 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,112 @@ +# Hacking on Quorum / various notes + +## How does private state work? + +Original commit from Jeff explains the dual public and private state with INITIAL restrictions: +``` +commit 763f939f4725daa136161868d3b01fa7a84eb71e +Author: Jeffrey Wilcke +Date: Mon Oct 31 12:46:40 2016 +0100 + + core, core/vm: dual state & read only EVM + + This commit implements a dual state approach. The dual state approach + separates public and private state by making the core vm environment + context aware. + + Although not currently implemented it will need to prohibit value + transfers and it must initialise all transactions from accounts on the + public state. This means that sending transactions increments the + account nonce on the public state and contract addresses are derived + from the public state when initialised by a transaction. For obvious + reasons, contract created by private contracts are still derived from + public state. + + This is required in order to have consensus over the public state at all + times as non-private participants would still process the transaction on + the public state even though private payload can not be decrypted. This + means that participants of a private group must do the same in order to + have public consensus. However the creation of the contract and + interaction still occurs on the private state. + + It implements support for the following calling model: + + S: sender, (X): private, X: public, ->: direction, [ ]: read only mode + + 1. S -> A -> B + 2. S -> (A) -> (B) + 3. S -> (A) -> [ B -> C ] + + It does not support + + 1. (S) -> A + 2. (S) -> (A) + 3. S -> (A) -> B + + Implemented "read only" mode for the EVM. Read only mode is checked + during any opcode that could potentially modify the state. If such an + opcode is encountered during "read only", it throws an exception. + + The EVM is flagged "read only" when a private contract calls in to + public state. +``` + + +Some things have changed since, let's look at the EVM structure in some more detail: + +```go +type EVM struct { + ... + // StateDB gives access to the underlying state + StateDB StateDB + // Depth is the current call stack + depth int + ... + + publicState PublicState + privateState PrivateState + states [1027]*state.StateDB + currentStateDepth uint + readOnly bool + readOnlyDepth uint +} +``` + +The vanilla EVM has a call depth limit of 1024. Our `states` parallel the EVM call stack, recording as contracts in the public and private state call back and forth to each other. Note it doesn't have to be a "public -> private -> public -> private" back-and-forth chain. It can be any sequence of { public, private }. + +The interface for calling is this `Push` / `Pop` sequence: + +```go +evm.Push(getDualState(evm, addr)) +defer func() { evm.Pop() }() +// ... do work in the pushed state +``` + +The definitions of `Push` and `Pop` are simple and important enough to duplicate here: + +```go +func (env *EVM) Push(statedb StateDB) { + if env.privateState != statedb { + env.readOnly = true + env.readOnlyDepth = env.currentStateDepth + } + + if castedStateDb, ok := statedb.(*state.StateDB); ok { + env.states[env.currentStateDepth] = castedStateDb + env.currentStateDepth++ + } + + env.StateDB = statedb +} +func (env *EVM) Pop() { + env.currentStateDepth-- + if env.readOnly && env.currentStateDepth == env.readOnlyDepth { + env.readOnly = false + } + env.StateDB = env.states[env.currentStateDepth-1] +} +``` + +Note the invariant that `StateDB` always points to the current state db. + +The other interesting note is read only mode. Any time we call from the private state into the public state (`env.privateState != statedb`), we require anything deeper to be *read only*. Private state transactions can't affect public state, so we throw an EVM exception on any mutating operation (`SELFDESTRUCT, CREATE, SSTORE, LOG0, LOG1, LOG2, LOG3, LOG4`). Question: have any more mutating operations been added? Question: could we not mutate deeper private state? diff --git a/README.md b/README.md index ddb885dfdc..dba9a6f9f9 100644 --- a/README.md +++ b/README.md @@ -1,359 +1,104 @@ -## Go Ethereum +# -Official Golang implementation of the Ethereum protocol. +Quorum Slack +![Build Check](https://github.com/jpmorganchase/quorum/workflows/Build%20Check/badge.svg?branch=master) +[![Download](https://api.bintray.com/packages/quorumengineering/quorum/geth/images/download.svg)](https://bintray.com/quorumengineering/quorum/geth/_latestVersion) +[![Docker Pulls](https://img.shields.io/docker/pulls/quorumengineering/quorum)](https://hub.docker.com/r/quorumengineering/quorum) -[![API Reference]( -https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667 -)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) -[![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) -[![Travis](https://travis-ci.org/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -[![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv) +Quorum is an Ethereum-based distributed ledger protocol with transaction/contract privacy and new consensus mechanisms. -Automated builds are available for stable releases and the unstable master branch. Binary -archives are published at https://geth.ethereum.org/downloads/. +Quorum is a fork of [go-ethereum](https://github.com/ethereum/go-ethereum) and is updated in line with go-ethereum releases. -## Building the source +Key enhancements over go-ethereum: -For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. +* [__Privacy__](http://docs.goquorum.com/en/latest/Privacy/Overview/) - Quorum supports private transactions and private contracts through public/private state separation, and utilises peer-to-peer encrypted message exchanges (see [Constellation](https://github.com/consensys/constellation) and [Tessera](https://github.com/consensys/tessera)) for directed transfer of private data to network participants +* [__Alternative Consensus Mechanisms__](http://docs.goquorum.com/en/latest/Consensus/Consensus/) - with no need for POW/POS in a permissioned network, Quorum instead offers multiple consensus mechanisms that are more appropriate for consortium chains: + * [__Raft-based Consensus__](http://docs.goquorum.com/en/latest/Consensus/raft/raft/) - a consensus model for faster blocktimes, transaction finality, and on-demand block creation + * [__Istanbul BFT__](http://docs.goquorum.com/en/latest/Consensus/ibft/ibft/) - a PBFT-inspired consensus algorithm with transaction finality, by AMIS. + * [__Clique POA Consensus__](https://github.com/ethereum/EIPs/issues/225) - a default POA consensus algorithm bundled with Go Ethereum. +* [__Peer Permissioning__](http://docs.goquorum.com/en/latest/Permissioning/Permissions%20Overview/) - node/peer permissioning, ensuring only known parties can join the network +* [__Account Management__](http://docs.goquorum.com/en/latest/Account-Key-Management/Overview/) - Quorum introduced account plugins, which allows Quorum or clef to be extended with alternative methods of managing accounts including external vaults. +* [__Pluggable Architecture__](http://docs.goquorum.com/en/latest/PluggableArchitecture/Overview/) - allows adding additional features as plugins to the core `geth`, providing extensibility, flexibility, and distinct isolation of Quorum features. +* __Higher Performance__ - Quorum offers significantly higher performance throughput than public geth -Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install -them using your favourite package manager. Once the dependencies are installed, run +## Architecture -```shell -make geth -``` +![Quorum Tessera Privacy Flow](https://github.com/consensys/quorum/blob/master/docs/Quorum%20Design.png) -or, to build the full suite of utilities: +The above diagram is very high-level overview of component architecture used by Quorum. For more in-depth discussion of the components and how they interact, please refer to [lifecycle of a private transaction](http://docs.goquorum.com/en/latest/Privacy/Lifecycle-of-a-private-transaction/). -```shell -make all -``` +## Quickstart +There are [several ways](https://docs.goquorum.com/en/latest/Getting%20Started/Getting%20Started%20Overview/) to quickly get up and running with Quorum. One of the easiest is to use [Quorum Wizard](https://docs.goquorum.com/en/latest/Getting%20Started/Getting%20Started%20Overview/#quickstart-with-quorum-wizard) - a command line tool that allows users to set up a development Quorum network on their local machine in less than *2 minutes*. -## Executables +## Quorum Projects -The go-ethereum project comes with several wrappers/executables found in the `cmd` -directory. +Check out some of the interesting projects we are actively working on: -| Command | Description | -| :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. | -| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. | -| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | -| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | -| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | -| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | -| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. | +* [quorum-wizard](http://docs.goquorum.com/en/latest/Wizard/GettingStarted/): Setup a Quorum network in 2 minutes! +* [quorum-remix-plugin](http://docs.goquorum.com/en/latest/RemixPlugin/Overview/): The Quorum plugin for Ethereum's Remix IDE adds support for creating and interacting with private contracts on a Quorum network. +* [Cakeshop](http://docs.goquorum.com/en/latest/Cakeshop/Overview/): An integrated development environment and SDK for Quorum +* [Quorum-Profiling](http://docs.goquorum.com/en/latest/Quorum%20Profiling/Overview/): Toolset for stress testing & benchmarking Quorum networks. +* [quorum-examples](http://docs.goquorum.com/en/latest/Getting%20Started/Quorum-Examples/): Quorum demonstration examples +* [qubernetes](http://docs.goquorum.com/en/latest/Getting%20Started/Getting%20Started%20Overview/#quorum-on-kubernetes): Deploy Quorum on Kubernetes +* [quorum-cloud](http://docs.goquorum.com/en/latest/Getting%20Started/Getting%20Started%20Overview/#creating-a-network-deployed-in-the-cloud): Tools to help deploy Quorum network in a cloud provider of choice +* [quorum.js](http://docs.goquorum.com/en/latest/quorum.js/Overview/): Extends web3.js to support Quorum-specific APIs +* Zero Knowledge on Quorum + * [ZSL](https://docs.goquorum.consensys.net/en/latest/Reference/GoQuorum-Projects/#zsl-proof-of-concept) POC and [ZSL on Quorum](https://github.com/ConsenSys/zsl-q/blob/master/README.md) + * [Anonymous Zether](https://github.com/ConsenSys/anonymous-zether) implementation -## Running `geth` -Going through all the possible command line flags is out of scope here (please consult our -[CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)), -but we've enumerated a few common parameter combos to get you up to speed quickly -on how you can run your own `geth` instance. -### Full node on the main Ethereum network +## Official Docker Containers +The official docker containers can be found under https://hub.docker.com/u/quorumengineering/ -By far the most common scenario is people wanting to simply interact with the Ethereum -network: create accounts; transfer funds; deploy and interact with contracts. For this -particular use-case the user doesn't care about years-old historical data, so we can -fast-sync quickly to the current state of the network. To do so: +## Third Party Tools/Libraries -```shell -$ geth console -``` +The following Quorum-related libraries/applications have been created by Third Parties and as such are not specifically endorsed by J.P. Morgan. A big thanks to the developers for improving the tooling around Quorum! -This command will: - * Start `geth` in fast sync mode (default, can be changed with the `--syncmode` flag), - causing it to download more data in exchange for avoiding processing the entire history - of the Ethereum network, which is very CPU intensive. - * Start up `geth`'s built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console), - (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API) - as well as `geth`'s own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs). - This tool is optional and if you leave it out you can always attach to an already running - `geth` instance with `geth attach`. +* [Quorum Blockchain Explorer](https://github.com/web3labs/epirus-free) - a Blockchain Explorer for Quorum which supports viewing private transactions +* [Quorum-Genesis](https://github.com/davebryson/quorum-genesis) - A simple CL utility for Quorum to help populate the genesis file with voters and makers +* [Quorum Maker](https://github.com/synechron-finlabs/quorum-maker/) - a utility to create Quorum nodes +* [QuorumNetworkManager](https://github.com/ConsenSys/QuorumNetworkManager) - makes creating & managing Quorum networks easy +* [ERC20 REST service](https://github.com/web3labs/erc20-rest-service) - a Quorum-supported RESTful service for creating and managing ERC-20 tokens +* [Nethereum Quorum](https://github.com/Nethereum/Nethereum/tree/master/src/Nethereum.Quorum) - a .NET Quorum adapter +* [web3j-quorum](https://github.com/web3j/web3j-quorum) - an extension to the web3j Java library providing support for the Quorum API +* [Apache Camel](http://github.com/apache/camel) - an Apache Camel component providing support for the Quorum API using web3j library. Here is the artcile describing how to use Apache Camel with Ethereum and Quorum https://medium.com/@bibryam/enterprise-integration-for-ethereum-fa67a1577d43 -### A Full node on the Görli test network +## Contributing +Quorum is built on open source and we invite you to contribute enhancements. Upon review you will be required to complete a Contributor License Agreement (CLA) before we are able to merge. If you have any questions about the contribution process, please feel free to send an email to [info@goquorum.com](mailto:info@goquorum.com). Please see the [Contributors guide](.github/CONTRIBUTING.md) for more information about the process. -Transitioning towards developers, if you'd like to play around with creating Ethereum -contracts, you almost certainly would like to do that without any real money involved until -you get the hang of the entire system. In other words, instead of attaching to the main -network, you want to join the **test** network with your node, which is fully equivalent to -the main network, but with play-Ether only. +## Reporting Security Bugs +Security is part of our commitment to our users. At Quorum we have a close relationship with the security community, we understand the realm, and encourage security researchers to become part of our mission of building secure reliable software. This section explains how to submit security bugs, and what to expect in return. -```shell -$ geth --goerli console -``` +All security bugs in [Quorum](https://github.com/consensys/quorum) and its ecosystem ([Tessera](https://github.com/consensys/tessera), [Constellation](https://github.com/consensys/constellation), [Cakeshop](https://github.com/consensys/cakeshop), ..etc) should be reported by email to [security-quorum@consensys.net](mailto:security-quorum@consensys.net). Please use the prefix **[security]** in your subject. This email is delivered to Quorum security team. Your email will be acknowledged, and you'll receive a more detailed response to your email as soon as possible indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. -The `console` subcommand has the exact same meaning as above and they are equally -useful on the testnet too. Please, see above for their explanations if you've skipped here. +If you have not received a reply to your email or you have not heard from the security team please contact any team member through quorum slack security channel. **Please note that Quorum slack channels are public discussion forum**. When escalating to this medium, please do not disclose the details of the issue. Simply state that you're trying to reach a member of the security team. -Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit: +#### Responsible Disclosure Process +Quorum project uses the following responsible disclosure process: - * Instead of connecting the main Ethereum network, the client will connect to the Görli - test network, which uses different P2P bootnodes, different network IDs and genesis - states. - * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth` - will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on - Linux). Note, on OSX and Linux this also means that attaching to a running testnet node - requires the use of a custom endpoint since `geth attach` will try to attach to a - production node endpoint by default, e.g., - `geth attach /goerli/geth.ipc`. Windows users are not affected by - this. +- Once the security report is received it is assigned a primary handler. This person coordinates the fix and release process. +- The issue is confirmed and a list of affected software is determined. +- Code is audited to find any potential similar problems. +- If it is determined, in consultation with the submitter, that a CVE-ID is required, the primary handler will trigger the process. +- Fixes are applied to the public repository and a new release is issued. +- On the date that the fixes are applied, announcements are sent to Quorum-announce. +- At this point you would be able to disclose publicly your finding. -*Note: Although there are some internal protective measures to prevent transactions from -crossing over between the main network and test network, you should make sure to always -use separate accounts for play-money and real-money. Unless you manually move -accounts, `geth` will by default correctly separate the two networks and will not make any -accounts available between them.* +**Note:** This process can take some time. Every effort will be made to handle the security bug in as timely a manner as possible, however it's important that we follow the process described above to ensure that disclosures are handled consistently. -### Full node on the Rinkeby test network +#### Receiving Security Updates +The best way to receive security announcements is to subscribe to the Quorum-announce mailing list/channel. Any messages pertaining to a security issue will be prefixed with **[security]**. -Go Ethereum also supports connecting to the older proof-of-authority based test network -called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community. - -```shell -$ geth --rinkeby console -``` - -### Full node on the Ropsten test network - -In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The -Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such, -it has certain extra overhead and is more susceptible to reorganization attacks due to the -network's low difficulty/security. - -```shell -$ geth --ropsten console -``` - -*Note: Older Geth configurations store the Ropsten database in the `testnet` subdirectory.* - -### Configuration - -As an alternative to passing the numerous flags to the `geth` binary, you can also pass a -configuration file via: - -```shell -$ geth --config /path/to/your_config.toml -``` - -To get an idea how the file should look like you can use the `dumpconfig` subcommand to -export your existing configuration: - -```shell -$ geth --your-favourite-flags dumpconfig -``` - -*Note: This works only with `geth` v1.6.0 and above.* - -#### Docker quick start - -One of the quickest ways to get Ethereum up and running on your machine is by using -Docker: - -```shell -docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \ - -p 8545:8545 -p 30303:30303 \ - ethereum/client-go -``` - -This will start `geth` in fast-sync mode with a DB memory allowance of 1GB just as the -above command does. It will also create a persistent volume in your home directory for -saving your blockchain as well as map the default ports. There is also an `alpine` tag -available for a slim version of the image. - -Do not forget `--http.addr 0.0.0.0`, if you want to access RPC from other containers -and/or hosts. By default, `geth` binds to the local interface and RPC endpoints is not -accessible from the outside. - -### Programmatically interfacing `geth` nodes - -As a developer, sooner rather than later you'll want to start interacting with `geth` and the -Ethereum network via your own programs and not manually through the console. To aid -this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) -and [`geth` specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). -These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based -platforms, and named pipes on Windows). - -The IPC interface is enabled by default and exposes all the APIs supported by `geth`, -whereas the HTTP and WS interfaces need to manually be enabled and only expose a -subset of APIs due to security reasons. These can be turned on/off and configured as -you'd expect. - -HTTP based JSON-RPC API options: - - * `--http` Enable the HTTP-RPC server - * `--http.addr` HTTP-RPC server listening interface (default: `localhost`) - * `--http.port` HTTP-RPC server listening port (default: `8545`) - * `--http.api` API's offered over the HTTP-RPC interface (default: `eth,net,web3`) - * `--http.corsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced) - * `--ws` Enable the WS-RPC server - * `--ws.addr` WS-RPC server listening interface (default: `localhost`) - * `--ws.port` WS-RPC server listening port (default: `8546`) - * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`) - * `--ws.origins` Origins from which to accept websockets requests - * `--ipcdisable` Disable the IPC-RPC server - * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,shh,txpool,web3`) - * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it) - -You'll need to use your own programming environments' capabilities (libraries, tools, etc) to -connect via HTTP, WS or IPC to a `geth` node configured with the above flags and you'll -need to speak [JSON-RPC](https://www.jsonrpc.org/specification) on all transports. You -can reuse the same connection for multiple requests! - -**Note: Please understand the security implications of opening up an HTTP/WS based -transport before doing so! Hackers on the internet are actively trying to subvert -Ethereum nodes with exposed APIs! Further, all browser tabs can access locally -running web servers, so malicious web pages could try to subvert locally available -APIs!** - -### Operating a private network - -Maintaining your own private network is more involved as a lot of configurations taken for -granted in the official networks need to be manually set up. - -#### Defining the private genesis state - -First, you'll need to create the genesis state of your networks, which all nodes need to be -aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`): - -```json -{ - "config": { - "chainId": , - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0 - }, - "alloc": {}, - "coinbase": "0x0000000000000000000000000000000000000000", - "difficulty": "0x20000", - "extraData": "", - "gasLimit": "0x2fefd8", - "nonce": "0x0000000000000042", - "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x00" -} -``` - -The above fields should be fine for most purposes, although we'd recommend changing -the `nonce` to some random value so you prevent unknown remote nodes from being able -to connect to you. If you'd like to pre-fund some accounts for easier testing, create -the accounts and populate the `alloc` field with their addresses. - -```json -"alloc": { - "0x0000000000000000000000000000000000000001": { - "balance": "111111111" - }, - "0x0000000000000000000000000000000000000002": { - "balance": "222222222" - } -} -``` - -With the genesis state defined in the above JSON file, you'll need to initialize **every** -`geth` node with it prior to starting it up to ensure all blockchain parameters are correctly -set: - -```shell -$ geth init path/to/genesis.json -``` - -#### Creating the rendezvous point - -With all nodes that you want to run initialized to the desired genesis state, you'll need to -start a bootstrap node that others can use to find each other in your network and/or over -the internet. The clean way is to configure and run a dedicated bootnode: - -```shell -$ bootnode --genkey=boot.key -$ bootnode --nodekey=boot.key -``` - -With the bootnode online, it will display an [`enode` URL](https://github.com/ethereum/wiki/wiki/enode-url-format) -that other nodes can use to connect to it and exchange peer information. Make sure to -replace the displayed IP address information (most probably `[::]`) with your externally -accessible IP to get the actual `enode` URL. - -*Note: You could also use a full-fledged `geth` node as a bootnode, but it's the less -recommended way.* - -#### Starting up your member nodes - -With the bootnode operational and externally reachable (you can try -`telnet ` to ensure it's indeed reachable), start every subsequent `geth` -node pointed to the bootnode for peer discovery via the `--bootnodes` flag. It will -probably also be desirable to keep the data directory of your private network separated, so -do also specify a custom `--datadir` flag. - -```shell -$ geth --datadir=path/to/custom/data/folder --bootnodes= -``` - -*Note: Since your network will be completely cut off from the main and test networks, you'll -also need to configure a miner to process transactions and create new blocks for you.* - -#### Running a private miner - -Mining on the public Ethereum network is a complex task as it's only feasible using GPUs, -requiring an OpenCL or CUDA enabled `ethminer` instance. For information on such a -setup, please consult the [EtherMining subreddit](https://www.reddit.com/r/EtherMining/) -and the [ethminer](https://github.com/ethereum-mining/ethminer) repository. - -In a private network setting, however a single CPU miner instance is more than enough for -practical purposes as it can produce a stable stream of blocks at the correct intervals -without needing heavy resources (consider running on a single thread, no need for multiple -ones either). To start a `geth` instance for mining, run it with all your usual flags, extended -by: - -```shell -$ geth --mine --miner.threads=1 --etherbase=0x0000000000000000000000000000000000000000 -``` - -Which will start mining blocks and transactions on a single CPU thread, crediting all -proceedings to the account specified by `--etherbase`. You can further tune the mining -by changing the default gas limit blocks converge to (`--targetgaslimit`) and the price -transactions are accepted at (`--gasprice`). - -## Contribution - -Thank you for considering to help out with the source code! We welcome contributions -from anyone on the internet, and are grateful for even the smallest of fixes! - -If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request -for the maintainers to review and merge into the main code base. If you wish to submit -more complex changes though, please check up with the core devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) -to ensure those changes are in line with the general philosophy of the project and/or get -some early feedback which can make both your efforts much lighter as well as our review -and merge procedures quick and simple. - -Please make sure your contributions adhere to our coding guidelines: - - * Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) - guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). - * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) - guidelines. - * Pull requests need to be based on and opened against the `master` branch. - * Commit messages should be prefixed with the package(s) they modify. - * E.g. "eth, rpc: make trace configs optional" - -Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) -for more details on configuring your environment, managing project dependencies, and -testing procedures. +Comments on This Policy +If you have any suggestions to improve this policy, please send an email to info@goquorum.com for discussion. ## License The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the -[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), -also included in our repository in the `COPYING.LESSER` file. +[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), also +included in our repository in the `COPYING.LESSER` file. The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the -[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also -included in our repository in the `COPYING` file. +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also included +in our repository in the `COPYING` file. diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index e51f0bd8ea..4865eb7bdd 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -94,3 +94,19 @@ func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) }, } } + +// Quorum +// +// NewWalletTransactor is a utility method to easily create a transaction signer +// from a wallet account +func NewWalletTransactor(w accounts.Wallet, account accounts.Account) *TransactOpts { + return &TransactOpts{ + From: account.Address, + Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, errors.New("not authorized to sign this account") + } + return w.SignTx(account, tx, nil) // homestead signer without chainID is backward compatible + }, + } +} diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index ca60cc1b43..ca456e6f0e 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -81,7 +81,9 @@ type ContractTransactor interface { // for setting a reasonable default. EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) // SendTransaction injects the transaction into the pending pool for execution. - SendTransaction(ctx context.Context, tx *types.Transaction) error + SendTransaction(ctx context.Context, tx *types.Transaction, args PrivateTxArgs) error + // PreparePrivateTransaction send the private transaction to Tessera/Constellation's /storeraw API using HTTP + PreparePrivateTransaction(data []byte, privateFrom string) (common.EncryptedPayloadHash, error) } // ContractFilterer defines the methods needed to access log events using one-off diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 4b9372a201..d8cb18923a 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -37,12 +37,15 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "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. @@ -89,6 +92,20 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis return backend } +// Quorum +// +// Create a simulated backend based on existing Ethereum service +func NewSimulatedBackendFrom(ethereum *eth.Ethereum) *SimulatedBackend { + backend := &SimulatedBackend{ + database: ethereum.ChainDb(), + blockchain: ethereum.BlockChain(), + config: ethereum.BlockChain().Config(), + events: filters.NewEventSystem(&filterBackend{ethereum.ChainDb(), ethereum.BlockChain()}, false), + } + backend.rollback() + return backend +} + // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { @@ -123,7 +140,7 @@ func (b *SimulatedBackend) Rollback() { func (b *SimulatedBackend) rollback() { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) - statedb, _ := b.blockchain.State() + statedb, _, _ := b.blockchain.State() b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) @@ -132,13 +149,15 @@ func (b *SimulatedBackend) rollback() { // stateByBlockNumber retrieves a state by a given blocknumber. func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) { if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 { - return b.blockchain.State() + statedb, _, err := b.blockchain.State() + return statedb, err } block, err := b.blockByNumberNoLock(ctx, blockNumber) if err != nil { return nil, err } - return b.blockchain.StateAt(block.Root()) + statedb, _, err := b.blockchain.StateAt(block.Root()) + return statedb, err } // CodeAt returns the code associated with a certain account in the blockchain. @@ -150,7 +169,7 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, if err != nil { return nil, err } - + statedb, _, _ = b.blockchain.State() return statedb.GetCode(contract), nil } @@ -163,7 +182,7 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres if err != nil { return nil, err } - + statedb, _, _ = b.blockchain.State() return statedb.GetBalance(contract), nil } @@ -176,7 +195,7 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, if err != nil { return 0, err } - + statedb, _, _ = b.blockchain.State() return statedb.GetNonce(contract), nil } @@ -189,7 +208,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres if err != nil { return nil, err } - + statedb, _, _ = b.blockchain.State() val := statedb.GetState(contract, key) return val[:], nil } @@ -383,11 +402,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { return nil, errBlockNumberUnsupported } - state, err := b.blockchain.State() + state, _, err := b.blockchain.State() if err != nil { return nil, err } - res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state, state) if err != nil { return nil, err } @@ -404,7 +423,7 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu defer b.mu.Unlock() defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState, b.pendingState) if err != nil { return nil, err } @@ -475,7 +494,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs call.Gas = gas snapshot := b.pendingState.Snapshot() - res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState, b.pendingState) b.pendingState.RevertToSnapshot(snapshot) if err != nil { @@ -525,7 +544,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs // callContract implements common code between normal and pending contract calls. // state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) { +func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB, privateState *state.StateDB) (*core.ExecutionResult, error) { // Ensure message is initialized properly. if call.GasPrice == nil { call.GasPrice = big.NewInt(1) @@ -545,7 +564,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) + vmenv := vm.NewEVM(evmContext, statedb, privateState, b.config, vm.Config{}) gaspool := new(core.GasPool).AddGas(math.MaxUint64) return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() @@ -553,7 +572,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // SendTransaction updates the pending block to include the given transaction. // It panics if the transaction is invalid. -func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { +func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction, args bind.PrivateTxArgs) error { b.mu.Lock() defer b.mu.Unlock() @@ -572,13 +591,18 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa } block.AddTxWithChain(b.blockchain, tx) }) - statedb, _ := b.blockchain.State() + statedb, _, _ := b.blockchain.State() b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) return nil } +// PreparePrivateTransaction dummy implementation +func (b *SimulatedBackend) PreparePrivateTransaction(data []byte, privateFrom string) (common.EncryptedPayloadHash, error) { + return common.EncryptedPayloadHash{}, nil +} + // FilterLogs executes a log filter operation, blocking during execution and // returning all the results in one batch. // @@ -685,7 +709,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { } block.OffsetTime(int64(adjustment.Seconds())) }) - statedb, _ := b.blockchain.State() + statedb, _, _ := b.blockchain.State() b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) @@ -783,6 +807,18 @@ func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.Matche 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") +} + func nullSubscription() event.Subscription { return event.NewSubscription(func(quit <-chan struct{}) error { <-quit diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index d14a88e8bb..c24f34519a 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -63,7 +63,7 @@ func TestSimulatedBackend(t *testing.T) { tx := types.NewContractCreation(0, big.NewInt(0), gas, big.NewInt(1), common.FromHex(code)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) - err = sim.SendTransaction(context.Background(), tx) + err = sim.SendTransaction(context.Background(), tx, bind.PrivateTxArgs{}) if err != nil { t.Fatal("error sending transaction") } @@ -78,7 +78,7 @@ func TestSimulatedBackend(t *testing.T) { } sim.Commit() - _, isPending, err = sim.TransactionByHash(context.Background(), txHash) + tx, isPending, err = sim.TransactionByHash(context.Background(), txHash) if err != nil { t.Fatalf("error getting transaction with hash: %v", txHash.String()) } @@ -129,7 +129,7 @@ func TestNewSimulatedBackend(t *testing.T) { t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config) } - statedb, _ := sim.blockchain.State() + statedb, _, _ := sim.blockchain.State() bal := statedb.GetBalance(testAddr) if bal.Cmp(expectedBal) != 0 { t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) @@ -251,7 +251,7 @@ func TestSimulatedBackend_NonceAt(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } @@ -292,7 +292,7 @@ func TestSimulatedBackend_SendTransaction(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } @@ -327,7 +327,7 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } @@ -640,7 +640,7 @@ func TestSimulatedBackend_TransactionCount(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } @@ -695,7 +695,7 @@ func TestSimulatedBackend_TransactionInBlock(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } @@ -750,7 +750,7 @@ func TestSimulatedBackend_PendingNonceAt(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } @@ -771,7 +771,7 @@ func TestSimulatedBackend_PendingNonceAt(t *testing.T) { if err != nil { t.Errorf("could not sign tx: %v", err) } - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not send tx: %v", err) } @@ -802,7 +802,7 @@ func TestSimulatedBackend_TransactionReceipt(t *testing.T) { } // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) + err = sim.SendTransaction(bgCtx, signedTx, bind.PrivateTxArgs{}) if err != nil { t.Errorf("could not add tx to pending block: %v", err) } diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 311e4108cd..4e977ff87e 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -34,6 +34,13 @@ import ( // sign the transaction before submission. type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) +// Quorum +// +// Additional arguments in order to support transaction privacy +type PrivateTxArgs struct { + PrivateFor []string `json:"privateFor"` +} + // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { Pending bool // Whether to operate on the pending state or the last known one @@ -54,6 +61,10 @@ type TransactOpts struct { GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) + + // Quorum + PrivateFrom string // The public key of the Tessera/Constellation identity to send this tx from. + PrivateFor []string // The public keys of the Tessera/Constellation identities this tx is intended for. } // FilterOpts is the collection of options to fine tune filtering for events @@ -243,16 +254,38 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i } else { rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) } + + // Quorum + // If this transaction is private, we need to substitute the data payload + // with the hash of the transaction from tessera/constellation. + if opts.PrivateFor != nil { + var payload []byte + hash, err := c.transactor.PreparePrivateTransaction(rawTx.Data(), opts.PrivateFrom) + if err != nil { + return nil, err + } + payload = hash.Bytes() + rawTx = c.createPrivateTransaction(rawTx, payload) + } + + // Choose signer to sign transaction if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") } - signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + var signedTx *types.Transaction + if rawTx.IsPrivate() { + signedTx, err = opts.Signer(types.QuorumPrivateTxSigner{}, opts.From, rawTx) + } else { + signedTx, err = opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + } if err != nil { return nil, err } - if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { + + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx, PrivateTxArgs{PrivateFor: opts.PrivateFor}); err != nil { return nil, err } + return signedTx, nil } @@ -368,6 +401,19 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) } +// Quorum +// createPrivateTransaction replaces the payload of private transaction to the hash from Tessera/Constellation +func (c *BoundContract) createPrivateTransaction(tx *types.Transaction, payload []byte) *types.Transaction { + var privateTx *types.Transaction + if tx.To() == nil { + privateTx = types.NewContractCreation(tx.Nonce(), tx.Value(), tx.Gas(), tx.GasPrice(), payload) + } else { + privateTx = types.NewTransaction(tx.Nonce(), c.address, tx.Value(), tx.Gas(), tx.GasPrice(), payload) + } + privateTx.SetPrivate() + return privateTx +} + // ensureContext is a helper method to ensure a context is not nil, even if the // user specified it as such. func ensureContext(ctx context.Context) context.Context { diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index a5f08499d2..56cdd372bf 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1751,6 +1751,13 @@ func TestGolangBindings(t *testing.T) { if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } + // Quorum - add package github.com/jpmorganchase/quorum/crypto/secp256k1 that is defined as a standalone module + secp256Replacer := exec.Command(gocmd, "mod", "edit", "-replace", "github.com/ethereum/go-ethereum/crypto/secp256k1=github.com/jpmorganchase/quorum/crypto/secp256k1@v0.0.0-20200804194033-c8f07379f487") // Repo root + secp256Replacer.Dir = pkg + if out, err := secp256Replacer.CombinedOutput(); err != nil { + t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) + } + // Test the entire package and report any failures cmd := exec.Command(gocmd, "test", "-v", "-count", "1") cmd.Dir = pkg diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index e57b03cfa6..400e5c7617 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -122,6 +122,8 @@ var ( // {{.Type}}ABI is the input ABI used to generate the binding from. const {{.Type}}ABI = "{{.InputABI}}" + var {{.Type}}ParsedABI, _ = abi.JSON(strings.NewReader({{.Type}}ABI)) + {{if $contract.FuncSigs}} // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. var {{.Type}}FuncSigs = map[string]string{ @@ -486,6 +488,7 @@ var ( return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil } + var {{.Normalized.Name}}TopicHash = "0x{{printf "%x" .Original.ID}}" // Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}. // // Solidity: {{.Original.String}} diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index 9f9b7a000d..d83cb0b412 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -79,7 +79,7 @@ func TestWaitDeployed(t *testing.T) { }() // Send and mine the transaction. - backend.SendTransaction(ctx, tx) + backend.SendTransaction(ctx, tx, bind.PrivateTxArgs{}) backend.Commit() select { @@ -111,7 +111,7 @@ func TestWaitDeployedCornerCases(t *testing.T) { tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - backend.SendTransaction(ctx, tx) + backend.SendTransaction(ctx, tx, bind.PrivateTxArgs{}) backend.Commit() notContentCreation := errors.New("tx is not contract creation") if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() { @@ -129,6 +129,6 @@ func TestWaitDeployedCornerCases(t *testing.T) { } }() - backend.SendTransaction(ctx, tx) + backend.SendTransaction(ctx, tx, bind.PrivateTxArgs{}) cancel() } diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 17a747db0e..4dd59ec187 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -204,13 +204,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio to = &t } args := &core.SendTxArgs{ - Data: &data, - Nonce: hexutil.Uint64(tx.Nonce()), - Value: hexutil.Big(*tx.Value()), - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: hexutil.Big(*tx.GasPrice()), - To: to, - From: common.NewMixedcaseAddress(account.Address), + Data: &data, + Nonce: hexutil.Uint64(tx.Nonce()), + Value: hexutil.Big(*tx.Value()), + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: hexutil.Big(*tx.GasPrice()), + To: to, + From: common.NewMixedcaseAddress(account.Address), + IsPrivate: tx.IsPrivate(), } var res signTransactionResult if err := api.client.Call(&res, "account_signTransaction", args); err != nil { diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go index fe9233c046..79e76ee392 100644 --- a/accounts/keystore/account_cache_test.go +++ b/accounts/keystore/account_cache_test.go @@ -299,7 +299,7 @@ func TestCacheFind(t *testing.T) { func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { var list []accounts.Account - for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { + for d := 200 * time.Millisecond; d < 16*time.Second; d *= 2 { list = ks.Accounts() if reflect.DeepEqual(list, wantAccounts) { // ks should have also received change notifications diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 9d5e2cf6a2..c6466e5000 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" ) var ( @@ -283,6 +284,13 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b if !found { return nil, ErrLocked } + + // start quorum specific + if tx.IsPrivate() { + log.Info("Private transaction signing with QuorumPrivateTxSigner") + return types.SignTx(tx, types.QuorumPrivateTxSigner{}, unlockedKey.PrivateKey) + } // End quorum specific + // Depending on the presence of the chain ID, sign with EIP155 or homestead if chainID != nil { return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) @@ -311,6 +319,9 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, } defer zeroKey(key.PrivateKey) + if tx.IsPrivate() { + return types.SignTx(tx, types.QuorumPrivateTxSigner{}, key.PrivateKey) + } // Depending on the presence of the chain ID, sign with EIP155 or homestead if chainID != nil { return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) diff --git a/accounts/manager.go b/accounts/manager.go index acf41ed8e9..8847da8cb4 100644 --- a/accounts/manager.go +++ b/accounts/manager.go @@ -136,6 +136,22 @@ func (am *Manager) Backends(kind reflect.Type) []Backend { return am.backends[kind] } +// Quorum +func (am *Manager) Backend(account Account) (Backend, error) { + for _, b := range am.backends { + for _, bb := range b { + for _, w := range bb.Wallets() { + if w.Contains(account) { + return bb, nil + } + } + } + } + return nil, ErrUnknownWallet +} + +// end Quorum + // Wallets returns all signer accounts registered under this account manager. func (am *Manager) Wallets() []Wallet { am.lock.RLock() diff --git a/accounts/pluggable/backend.go b/accounts/pluggable/backend.go new file mode 100644 index 0000000000..d2a673c6ae --- /dev/null +++ b/accounts/pluggable/backend.go @@ -0,0 +1,74 @@ +package pluggable + +import ( + "reflect" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/event" + plugin "github.com/ethereum/go-ethereum/plugin/account" +) + +var BackendType = reflect.TypeOf(&Backend{}) + +type Backend struct { + wallets []accounts.Wallet +} + +func NewBackend() *Backend { + return &Backend{ + wallets: []accounts.Wallet{ + &wallet{ + url: accounts.URL{ + Scheme: "plugin", + Path: "account", + }, + }, + }, + } +} + +func (b *Backend) Wallets() []accounts.Wallet { + cpy := make([]accounts.Wallet, len(b.wallets)) + copy(cpy, b.wallets) + return cpy +} + +// Subscribe implements accounts.Backend, creating a new subscription that is a no-op and simply exits when the Unsubscribe is called +func (b *Backend) Subscribe(_ chan<- accounts.WalletEvent) event.Subscription { + return event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) +} + +func (b *Backend) SetPluginService(s plugin.Service) error { + return b.wallet().setPluginService(s) +} + +func (b *Backend) TimedUnlock(account accounts.Account, password string, duration time.Duration) error { + return b.wallet().timedUnlock(account, password, duration) +} + +func (b *Backend) Lock(account accounts.Account) error { + return b.wallet().lock(account) +} + +// AccountCreator is the interface that wraps the plugin account creation methods. +// This interface is used to simplify the pluggable.Backend API available to the account plugin CLI and enables easier testing. +type AccountCreator interface { + NewAccount(newAccountConfig interface{}) (accounts.Account, error) + ImportRawKey(rawKey string, newAccountConfig interface{}) (accounts.Account, error) +} + +func (b *Backend) NewAccount(newAccountConfig interface{}) (accounts.Account, error) { + return b.wallet().newAccount(newAccountConfig) +} + +func (b *Backend) ImportRawKey(rawKey string, newAccountConfig interface{}) (accounts.Account, error) { + return b.wallet().importRawKey(rawKey, newAccountConfig) +} + +func (b *Backend) wallet() *wallet { + return b.wallets[0].(*wallet) +} diff --git a/accounts/pluggable/backend_test.go b/accounts/pluggable/backend_test.go new file mode 100644 index 0000000000..3836441500 --- /dev/null +++ b/accounts/pluggable/backend_test.go @@ -0,0 +1,131 @@ +package pluggable + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/pluggable/internal/testutils/mock_plugin" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func TestBackend_Subscribe_NoOp(t *testing.T) { + b := NewBackend() + + subscriber := make(chan accounts.WalletEvent, 4) + sub := b.Subscribe(subscriber) + require.NotNil(t, sub) + require.Len(t, subscriber, 0) + + sub.Unsubscribe() + require.Len(t, subscriber, 0) +} + +func TestBackend_Wallets_ReturnsCopy(t *testing.T) { + wallets := []accounts.Wallet{ + &wallet{ + url: accounts.URL{ + Scheme: "http", + Path: "url1", + }, + }, + &wallet{ + url: accounts.URL{ + Scheme: "http", + Path: "url2", + }, + }, + } + + b := NewBackend() + b.wallets = wallets + + got := b.Wallets() + got[0] = &wallet{ + url: accounts.URL{ + Scheme: "http", + Path: "changedurl", + }, + } + require.Equal(t, "changedurl", got[0].URL().Path) + + unchanged := b.Wallets() + require.Equal(t, "url1", unchanged[0].URL().Path) +} + +func TestBackend_TimedUnlock(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + TimedUnlock(gomock.Any(), gomock.Eq(acct1), gomock.Eq("pwd"), gomock.Eq(time.Minute)). + Return(nil) + + b := NewBackend() + b.wallets[0].(*wallet).pluginService = mockClient + + err := b.TimedUnlock(acct1, "pwd", time.Minute) + require.NoError(t, err) +} + +func TestBackend_Lock(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Lock(gomock.Any(), gomock.Eq(acct1)). + Return(nil) + + b := NewBackend() + b.wallets[0].(*wallet).pluginService = mockClient + + err := b.Lock(acct1) + require.NoError(t, err) +} + +func TestBackend_NewAccount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + newAccountConfig := struct{ config string }{config: "someconfig"} + newAccount := accounts.Account{} + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + NewAccount(gomock.Any(), gomock.Eq(newAccountConfig)). + Return(newAccount, nil) + + b := NewBackend() + b.wallets[0].(*wallet).pluginService = mockClient + + got, err := b.NewAccount(newAccountConfig) + require.NoError(t, err) + require.Equal(t, newAccount, got) +} + +func TestBackend_ImportRawKey(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + newAccountConfig := struct{ config string }{config: "someconfig"} + newAccount := accounts.Account{} + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + ImportRawKey(gomock.Any(), gomock.Eq("rawkey"), gomock.Eq(newAccountConfig)). + Return(newAccount, nil) + + b := NewBackend() + b.wallets[0].(*wallet).pluginService = mockClient + + got, err := b.ImportRawKey("rawkey", newAccountConfig) + require.NoError(t, err) + require.Equal(t, newAccount, got) +} diff --git a/accounts/pluggable/internal/testutils/matchers.go b/accounts/pluggable/internal/testutils/matchers.go new file mode 100644 index 0000000000..ee2a18d5bc --- /dev/null +++ b/accounts/pluggable/internal/testutils/matchers.go @@ -0,0 +1,19 @@ +package testutils + +import ( + "fmt" +) + +type PointerMatcher struct { + C chan<- interface{} +} + +func (m PointerMatcher) Matches(x interface{}) bool { + xAddr := fmt.Sprintf("%p", x) + CAddr := fmt.Sprintf("%p", m.C) + return xAddr == CAddr +} + +func (m PointerMatcher) String() string { + return fmt.Sprintf("is %v", m.C) +} diff --git a/accounts/pluggable/internal/testutils/mock_plugin/mock_plugin.go b/accounts/pluggable/internal/testutils/mock_plugin/mock_plugin.go new file mode 100644 index 0000000000..4bf9bf9ba5 --- /dev/null +++ b/accounts/pluggable/internal/testutils/mock_plugin/mock_plugin.go @@ -0,0 +1,196 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ethereum/go-ethereum/plugin/account (interfaces: Service) + +// Package mock_plugin is a generated GoMock package. +package mock_plugin + +import ( + context "context" + reflect "reflect" + time "time" + + accounts "github.com/ethereum/go-ethereum/accounts" + gomock "github.com/golang/mock/gomock" +) + +// MockService is a mock of Service interface +type MockService struct { + ctrl *gomock.Controller + recorder *MockServiceMockRecorder +} + +// MockServiceMockRecorder is the mock recorder for MockService +type MockServiceMockRecorder struct { + mock *MockService +} + +// NewMockService creates a new mock instance +func NewMockService(ctrl *gomock.Controller) *MockService { + mock := &MockService{ctrl: ctrl} + mock.recorder = &MockServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockService) EXPECT() *MockServiceMockRecorder { + return m.recorder +} + +// Accounts mocks base method +func (m *MockService) Accounts(arg0 context.Context) []accounts.Account { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Accounts", arg0) + ret0, _ := ret[0].([]accounts.Account) + return ret0 +} + +// Accounts indicates an expected call of Accounts +func (mr *MockServiceMockRecorder) Accounts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accounts", reflect.TypeOf((*MockService)(nil).Accounts), arg0) +} + +// Close mocks base method +func (m *MockService) Close(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close +func (mr *MockServiceMockRecorder) Close(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockService)(nil).Close), arg0) +} + +// Contains mocks base method +func (m *MockService) Contains(arg0 context.Context, arg1 accounts.Account) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Contains", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Contains indicates an expected call of Contains +func (mr *MockServiceMockRecorder) Contains(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contains", reflect.TypeOf((*MockService)(nil).Contains), arg0, arg1) +} + +// ImportRawKey mocks base method +func (m *MockService) ImportRawKey(arg0 context.Context, arg1 string, arg2 interface{}) (accounts.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImportRawKey", arg0, arg1, arg2) + ret0, _ := ret[0].(accounts.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ImportRawKey indicates an expected call of ImportRawKey +func (mr *MockServiceMockRecorder) ImportRawKey(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportRawKey", reflect.TypeOf((*MockService)(nil).ImportRawKey), arg0, arg1, arg2) +} + +// Lock mocks base method +func (m *MockService) Lock(arg0 context.Context, arg1 accounts.Account) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Lock", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Lock indicates an expected call of Lock +func (mr *MockServiceMockRecorder) Lock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockService)(nil).Lock), arg0, arg1) +} + +// NewAccount mocks base method +func (m *MockService) NewAccount(arg0 context.Context, arg1 interface{}) (accounts.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewAccount", arg0, arg1) + ret0, _ := ret[0].(accounts.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewAccount indicates an expected call of NewAccount +func (mr *MockServiceMockRecorder) NewAccount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAccount", reflect.TypeOf((*MockService)(nil).NewAccount), arg0, arg1) +} + +// Open mocks base method +func (m *MockService) Open(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Open", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Open indicates an expected call of Open +func (mr *MockServiceMockRecorder) Open(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockService)(nil).Open), arg0, arg1) +} + +// Sign mocks base method +func (m *MockService) Sign(arg0 context.Context, arg1 accounts.Account, arg2 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sign", arg0, arg1, arg2) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Sign indicates an expected call of Sign +func (mr *MockServiceMockRecorder) Sign(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*MockService)(nil).Sign), arg0, arg1, arg2) +} + +// Status mocks base method +func (m *MockService) Status(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Status indicates an expected call of Status +func (mr *MockServiceMockRecorder) Status(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockService)(nil).Status), arg0) +} + +// TimedUnlock mocks base method +func (m *MockService) TimedUnlock(arg0 context.Context, arg1 accounts.Account, arg2 string, arg3 time.Duration) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TimedUnlock", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// TimedUnlock indicates an expected call of TimedUnlock +func (mr *MockServiceMockRecorder) TimedUnlock(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TimedUnlock", reflect.TypeOf((*MockService)(nil).TimedUnlock), arg0, arg1, arg2, arg3) +} + +// UnlockAndSign mocks base method +func (m *MockService) UnlockAndSign(arg0 context.Context, arg1 accounts.Account, arg2 []byte, arg3 string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnlockAndSign", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UnlockAndSign indicates an expected call of UnlockAndSign +func (mr *MockServiceMockRecorder) UnlockAndSign(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockAndSign", reflect.TypeOf((*MockService)(nil).UnlockAndSign), arg0, arg1, arg2, arg3) +} diff --git a/accounts/pluggable/wallet.go b/accounts/pluggable/wallet.go new file mode 100644 index 0000000000..1941ab92ae --- /dev/null +++ b/accounts/pluggable/wallet.go @@ -0,0 +1,129 @@ +package pluggable + +import ( + "context" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + plugin "github.com/ethereum/go-ethereum/plugin/account" +) + +type wallet struct { + url accounts.URL + mu sync.Mutex + pluginService plugin.Service +} + +func (w *wallet) setPluginService(s plugin.Service) error { + w.mu.Lock() + defer w.mu.Unlock() + + w.pluginService = s + + return nil +} + +func (w *wallet) URL() accounts.URL { + return w.url +} + +func (w *wallet) Status() (string, error) { + return w.pluginService.Status(context.Background()) +} + +func (w *wallet) Open(passphrase string) error { + return w.pluginService.Open(context.Background(), passphrase) +} + +func (w *wallet) Close() error { + return w.pluginService.Close(context.Background()) +} + +func (w *wallet) Accounts() []accounts.Account { + return w.pluginService.Accounts(context.Background()) +} + +func (w *wallet) Contains(account accounts.Account) bool { + return w.pluginService.Contains(context.Background(), account) +} + +func (w *wallet) Derive(_ accounts.DerivationPath, _ bool) (accounts.Account, error) { + return accounts.Account{}, accounts.ErrNotSupported +} + +func (w *wallet) SelfDerive(_ []accounts.DerivationPath, _ ethereum.ChainStateReader) {} + +func (w *wallet) SignData(account accounts.Account, _ string, data []byte) ([]byte, error) { + return w.pluginService.Sign(context.Background(), account, crypto.Keccak256(data)) +} + +func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, _ string, data []byte) ([]byte, error) { + return w.pluginService.UnlockAndSign(context.Background(), account, crypto.Keccak256(data), passphrase) +} + +func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.pluginService.Sign(context.Background(), account, accounts.TextHash(text)) +} + +func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return w.pluginService.UnlockAndSign(context.Background(), account, accounts.TextHash(text), passphrase) +} + +func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + toSign, signer := prepareTxForSign(tx, chainID) + + sig, err := w.pluginService.Sign(context.Background(), account, toSign.Bytes()) + if err != nil { + return nil, err + } + + return tx.WithSignature(signer, sig) +} + +func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + toSign, signer := prepareTxForSign(tx, chainID) + + sig, err := w.pluginService.UnlockAndSign(context.Background(), account, toSign.Bytes(), passphrase) + if err != nil { + return nil, err + } + + return tx.WithSignature(signer, sig) +} + +func (w *wallet) timedUnlock(account accounts.Account, password string, duration time.Duration) error { + return w.pluginService.TimedUnlock(context.Background(), account, password, duration) +} + +func (w *wallet) lock(account accounts.Account) error { + return w.pluginService.Lock(context.Background(), account) +} + +func (w *wallet) newAccount(newAccountConfig interface{}) (accounts.Account, error) { + return w.pluginService.NewAccount(context.Background(), newAccountConfig) +} + +func (w *wallet) importRawKey(rawKey string, newAccountConfig interface{}) (accounts.Account, error) { + return w.pluginService.ImportRawKey(context.Background(), rawKey, newAccountConfig) +} + +// prepareTxForSign determines which Signer to use for the given tx and chainID, and returns the Signer's hash of the tx and the Signer itself +func prepareTxForSign(tx *types.Transaction, chainID *big.Int) (common.Hash, types.Signer) { + var s types.Signer + + if tx.IsPrivate() { + s = types.QuorumPrivateTxSigner{} + } else if chainID == nil { + s = types.HomesteadSigner{} + } else { + s = types.NewEIP155Signer(chainID) + } + + return s.Hash(tx), s +} diff --git a/accounts/pluggable/wallet_test.go b/accounts/pluggable/wallet_test.go new file mode 100644 index 0000000000..022486df01 --- /dev/null +++ b/accounts/pluggable/wallet_test.go @@ -0,0 +1,397 @@ +package pluggable + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/pluggable/internal/testutils/mock_plugin" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + scheme = "scheme" + + wltUrl = accounts.URL{ + Scheme: scheme, + Path: "uripath", + } + + acct1 = accounts.Account{ + Address: common.HexToAddress("0x4d6d744b6da435b5bbdde2526dc20e9a41cb72e5"), + URL: wltUrl, + } + + acct2 = accounts.Account{ + Address: common.HexToAddress("0x2332f90a329c2c55ba120b1449d36a144d1f9fe4"), + URL: accounts.URL{Scheme: scheme, Path: "path/to/file2.json"}, + } + acct3 = accounts.Account{ + Address: common.HexToAddress("0x992d7a8fca612c963796ecbfe78b300370b9545a"), + URL: accounts.URL{Scheme: scheme, Path: "path/to/file3.json"}, + } + acct4 = accounts.Account{ + Address: common.HexToAddress("0x39ac8f3ae3681b4422fdf808ae18ba4365e37da8"), + URL: accounts.URL{Scheme: scheme, Path: "path/to/file4.json"}, + } +) + +func validWallet(m *mock_plugin.MockService) *wallet { + return &wallet{ + url: wltUrl, + pluginService: m, + } +} + +func TestWallet_Url(t *testing.T) { + w := validWallet(nil) + got := w.URL() + assert.Equal(t, wltUrl, got) +} + +func TestWallet_Status(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + want := "status" + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Status(gomock.Any()). + Return(want, nil) + + w := validWallet(mockClient) + status, err := w.Status() + + assert.NoError(t, err) + assert.Equal(t, want, status) +} + +func TestWallet_Open(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Open(gomock.Any(), "pwd"). + Return(nil) + + w := validWallet(mockClient) + err := w.Open("pwd") + + assert.NoError(t, err) +} + +func TestWallet_Close(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Close(gomock.Any()). + Return(nil) + + w := validWallet(mockClient) + err := w.Close() + + assert.NoError(t, err) +} + +func TestWallet_Accounts(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + want := []accounts.Account{acct1, acct2, acct3, acct4} + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Accounts(gomock.Any()). + Return(want) + + w := validWallet(mockClient) + got := w.Accounts() + + assert.Equal(t, want, got) +} + +func TestWallet_Contains(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Contains(gomock.Any(), acct1). + Return(true) + + w := validWallet(mockClient) + got := w.Contains(acct1) + + assert.True(t, got) +} + +func TestWallet_Derive(t *testing.T) { + w := validWallet(nil) + _, err := w.Derive(accounts.DerivationPath{}, true) + if assert.Error(t, err) { + assert.Equal(t, accounts.ErrNotSupported, err) + } +} + +func TestWallet_SelfDerive(t *testing.T) { + w := validWallet(nil) + // does nothing + w.SelfDerive([]accounts.DerivationPath{}, nil) +} + +func TestWallet_SignData(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + toSign := []byte("somedata") + want := []byte("signeddata") + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Sign(gomock.Any(), acct1, crypto.Keccak256(toSign)). + Return(want, nil) + + w := validWallet(mockClient) + got, err := w.SignData(acct1, "", toSign) + + assert.NoError(t, err) + assert.Equal(t, want, got) +} + +func TestWallet_SignDataWithPassphrase(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + toSign := []byte("somedata") + want := []byte("signeddata") + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + UnlockAndSign(gomock.Any(), acct1, crypto.Keccak256(toSign), "pwd"). + Return(want, nil) + + w := validWallet(mockClient) + got, err := w.SignDataWithPassphrase(acct1, "pwd", "", toSign) + + assert.NoError(t, err) + assert.Equal(t, want, got) +} + +func TestWallet_SignText(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + toSign := []byte("somedata") + want := []byte("signeddata") + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Sign(gomock.Any(), acct1, accounts.TextHash(toSign)). + Return(want, nil) + + w := validWallet(mockClient) + got, err := w.SignText(acct1, toSign) + + assert.NoError(t, err) + assert.Equal(t, want, got) +} + +func TestWallet_SignTextWithPassphrase(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + toSign := []byte("somedata") + want := []byte("signeddata") + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + UnlockAndSign(gomock.Any(), acct1, accounts.TextHash(toSign), "pwd"). + Return(want, nil) + + w := validWallet(mockClient) + got, err := w.SignTextWithPassphrase(acct1, "pwd", toSign) + + assert.NoError(t, err) + assert.Equal(t, want, got) +} + +func TestWallet_SignTx(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + tests := []struct { + name string + isPrivate bool + chainID *big.Int + signer types.Signer + }{ + { + name: "Public EIP155 tx", + isPrivate: false, + chainID: big.NewInt(20), + signer: types.NewEIP155Signer(big.NewInt(20)), + }, + { + name: "Public Homestead tx", + isPrivate: false, + chainID: nil, + signer: types.HomesteadSigner{}, + }, + { + name: "Private tx", + isPrivate: true, + chainID: nil, + signer: types.QuorumPrivateTxSigner{}, + }, + } + + toSign := types.NewTransaction( + 1, + common.HexToAddress("0x2332f90a329c2c55ba120b1449d36a144d1f9fe4"), + big.NewInt(1), + 0, + big.NewInt(1), + nil, + ) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.isPrivate { + toSign.SetPrivate() + } + + hashToSign := tt.signer.Hash(toSign) + + mockSig := make([]byte, 65) + rand.Read(mockSig) + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + Sign(gomock.Any(), acct1, hashToSign.Bytes()). + Return(mockSig, nil) + + w := validWallet(mockClient) + got, err := w.SignTx(acct1, toSign, tt.chainID) + require.NoError(t, err) + + gotV, gotR, gotS := got.RawSignatureValues() + + wantR, wantS, wantV, err := tt.signer.SignatureValues(&types.Transaction{}, mockSig) // tx param is unused by method + require.NoError(t, err) + + // assert the correct signature is added to the tx + assert.Equal(t, wantV, gotV) + assert.Equal(t, wantR, gotR) + assert.Equal(t, wantS, gotS) + + // assert the rest of the tx is unchanged + assert.Equal(t, toSign.Nonce(), got.Nonce()) + assert.Equal(t, toSign.GasPrice(), got.GasPrice()) + assert.Equal(t, toSign.Gas(), got.Gas()) + assert.Equal(t, toSign.To(), got.To()) + assert.Equal(t, toSign.Value(), got.Value()) + assert.Equal(t, toSign.Data(), got.Data()) + }) + } +} + +func TestWallet_SignTxWithPassphrase(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + tests := []struct { + name string + isPrivate bool + chainID *big.Int + signer types.Signer + }{ + { + name: "Public EIP155 tx", + isPrivate: false, + chainID: big.NewInt(20), + signer: types.NewEIP155Signer(big.NewInt(20)), + }, + { + name: "Public Homestead tx", + isPrivate: false, + chainID: nil, + signer: types.HomesteadSigner{}, + }, + { + name: "Private tx", + isPrivate: true, + chainID: nil, + signer: types.QuorumPrivateTxSigner{}, + }, + } + + toSign := types.NewTransaction( + 1, + common.HexToAddress("0x2332f90a329c2c55ba120b1449d36a144d1f9fe4"), + big.NewInt(1), + 0, + big.NewInt(1), + nil, + ) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.isPrivate { + toSign.SetPrivate() + } + + hashToSign := tt.signer.Hash(toSign) + + mockSig := make([]byte, 65) + rand.Read(mockSig) + + mockClient := mock_plugin.NewMockService(ctrl) + mockClient. + EXPECT(). + UnlockAndSign(gomock.Any(), acct1, hashToSign.Bytes(), "pwd"). + Return(mockSig, nil) + + w := validWallet(mockClient) + got, err := w.SignTxWithPassphrase(acct1, "pwd", toSign, tt.chainID) + require.NoError(t, err) + + gotV, gotR, gotS := got.RawSignatureValues() + + wantR, wantS, wantV, err := tt.signer.SignatureValues(&types.Transaction{}, mockSig) // tx param is unused by method + require.NoError(t, err) + + // assert the correct signature is added to the tx + assert.Equal(t, wantV, gotV) + assert.Equal(t, wantR, gotR) + assert.Equal(t, wantS, gotS) + + // assert the rest of the tx is unchanged + assert.Equal(t, toSign.Nonce(), got.Nonce()) + assert.Equal(t, toSign.GasPrice(), got.GasPrice()) + assert.Equal(t, toSign.Gas(), got.Gas()) + assert.Equal(t, toSign.To(), got.To()) + assert.Equal(t, toSign.Value(), got.Value()) + assert.Equal(t, toSign.Data(), got.Data()) + }) + } +} diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index ee539d9653..9f6984bd1b 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -19,6 +19,7 @@ package usbwallet import ( "context" + "errors" "fmt" "io" "math/big" @@ -545,6 +546,10 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID w.stateLock.RLock() // Comms have own mutex, this is for the state fields defer w.stateLock.RUnlock() + if tx.IsPrivate() { + return nil, errors.New("Signing Quorum Private transactions with a USB wallet not yet supported") + } + // If the wallet is closed, abort if w.device == nil { return nil, accounts.ErrWalletClosed diff --git a/build/ci.go b/build/ci.go index 07edc80e59..3097512c1c 100644 --- a/build/ci.go +++ b/build/ci.go @@ -320,12 +320,16 @@ func doTest(cmdline []string) { if len(flag.CommandLine.Args()) > 0 { packages = flag.CommandLine.Args() } + // Quorum + // Ignore not Quorum related packages to accelerate build + packages = build.ExpandPackagesNoVendor(packages) + packages = build.IgnorePackages(packages) // Run the actual tests. // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. gotest := goTool("test", buildFlags(env)...) - gotest.Args = append(gotest.Args, "-p", "1") + gotest.Args = append(gotest.Args, "-p", "1", "--short") if *coverage { gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") } diff --git a/build/install-constellation-linux.sh b/build/install-constellation-linux.sh new file mode 100755 index 0000000000..17cb323a5d --- /dev/null +++ b/build/install-constellation-linux.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +curl -L https://github.com/jpmorganchase/constellation/releases/download/v0.3.2/constellation-0.3.2-ubuntu1604.tar.xz -o constellation.tar.xz + +tar xf constellation.tar.xz + +export PATH \ No newline at end of file diff --git a/build/install-constellation-mac.sh b/build/install-constellation-mac.sh new file mode 100755 index 0000000000..6bae76b0cb --- /dev/null +++ b/build/install-constellation-mac.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +echo "Going into $HOME" +cd $HOME + +echo "Cloning Constellation repo" +git clone https://github.com/jpmorganchase/constellation.git + +cd constellation + +echo "Running stack setup in $(pwd)" +stack setup + +echo "Now installing Constellation from $(pwd)" +stack install \ No newline at end of file diff --git a/build/travis-install-linux.sh b/build/travis-install-linux.sh new file mode 100755 index 0000000000..e18e2139e5 --- /dev/null +++ b/build/travis-install-linux.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e +# install geth and dependencies for acceptance tests +echo "---> install started ..." +echo "---> installing tools ..." +sudo apt-get update +# upgrade dpkg to fix issue with trusty: dpkg-deb: error +sudo apt-get -y install dpkg +java -version +mvn --version + +sudo wget https://github.com/ethereum/solidity/releases/download/v0.5.4/solc-static-linux -O /usr/local/bin/solc -q +sudo chmod +x /usr/local/bin/solc +solc --version +echo "---> tools installation done" + +echo "---> building geth ..." +sudo modprobe fuse +sudo chmod 666 /dev/fuse +sudo chown root:${USER} /etc/fuse.conf +go run build/ci.go install +echo "---> building geth done" + +echo "---> cloning quorum-cloud and quorum-acceptance-tests ..." +git clone https://github.com/jpmorganchase/quorum-acceptance-tests.git ${TRAVIS_HOME}/quorum-acceptance-tests +git clone https://github.com/jpmorganchase/quorum-cloud.git ${TRAVIS_HOME}/quorum-cloud + +echo "---> cloning done" + +echo "---> getting tessera jar ..." +wget https://oss.sonatype.org/service/local/repositories/releases/content/com/jpmorgan/quorum/tessera-app/0.10.4/tessera-app-0.10.4-app.jar -O $HOME/tessera.jar -q +echo "---> tessera done" + +echo "---> getting gauge jar ..." +wget https://github.com/getgauge/gauge/releases/download/v1.0.8/gauge-1.0.8-linux.x86_64.zip -O gauge.zip -q +sudo unzip -o gauge.zip -d /usr/local/bin +gauge telemetry off +cd ${TRAVIS_HOME}/quorum-acceptance-tests +gauge install +echo "---> gauge installation done" + +echo "---> install done" \ No newline at end of file diff --git a/build/travis-run-acceptance-tests-linux.sh b/build/travis-run-acceptance-tests-linux.sh new file mode 100755 index 0000000000..66ad3c5605 --- /dev/null +++ b/build/travis-run-acceptance-tests-linux.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e +# start network and run acceptance tests +echo "---> start quorum network for consensus ${TF_VAR_consensus_mechanism} ..." +java --version +export PATH=${TRAVIS_BUILD_DIR}/build/bin:$PATH +export TESSERA_JAR=${HOME}/tessera.jar +cd ${TRAVIS_HOME}/quorum-cloud/travis/4nodes +./init.sh ${TF_VAR_consensus_mechanism} +./start.sh ${TF_VAR_consensus_mechanism} tessera +echo "---> network started" +cd ${TRAVIS_HOME}/quorum-acceptance-tests +cp config/application-local.4nodes.yml config/application-local.yml +echo "---> run acceptance tests for consensus ${TF_VAR_consensus_mechanism} ..." +java --version +./src/travis/run_tests.sh +echo "---> acceptance tests finished" +echo "---> stop the network..." +${TRAVIS_HOME}/quorum-cloud/travis/4nodes/stop.sh +echo "---> network stopped" \ No newline at end of file diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 700f0a144b..50ba93b681 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -223,9 +223,10 @@ Response - `gas` [number]: maximum amount of gas to burn - `gasPrice` [number]: gas price - `value` [number:optional]: amount of Wei to send with the transaction - - `data` [data:optional]: input data + - `data` [data:optional]: input data (transaction manager hash if transaction is private) - `nonce` [number]: account nonce - 1. method signature [string:optional] + - `isPrivate` [boolean:optional]: whether the transaction is a Quorum private transaction + 3. method signature [string:optional] - The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected. diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 8c8778c249..7679daa744 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -34,20 +34,25 @@ import ( "runtime" "sort" "strings" + "syscall" "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/pluggable" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/plugin" + "github.com/ethereum/go-ethereum/plugin/account" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core" @@ -223,6 +228,11 @@ The gendoc generates example structures of the json-rpc communication types. `} ) +// +var supportedPlugins = []plugin.PluginInterfaceName{plugin.AccountPluginInterfaceName} + +// + // AppHelpFlagGroups is the application flags, grouped by functionality. var AppHelpFlagGroups = []flags.FlagGroup{ { @@ -249,6 +259,12 @@ var AppHelpFlagGroups = []flags.FlagGroup{ testFlag, advancedMode, acceptFlag, + // + utils.PluginSettingsFlag, + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.PluginSkipVerifyFlag, + // }, }, { @@ -285,6 +301,12 @@ func init() { advancedMode, acceptFlag, legacyRPCPortFlag, + // + utils.PluginSettingsFlag, + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.PluginSkipVerifyFlag, + // } app.Action = signer app.Commands = []cli.Command{initCommand, @@ -499,7 +521,10 @@ func newAccount(c *cli.Context) error { lightKdf = c.GlobalBool(utils.LightKDFFlag.Name) ) log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf) - am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") + am, _, err := startClefAccountManagerWithPlugins(c, ksLoc, true, lightKdf, "") + if err != nil { + return err + } // This gives is us access to the external API apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) // This gives us access to the internal API @@ -638,7 +663,12 @@ func signer(c *cli.Context) error { ) log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc, "light-kdf", lightKdf, "advanced", advanced) - am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath) + + am, pm, err := startClefAccountManagerWithPlugins(c, ksLoc, nousb, lightKdf, scpath) + if err != nil { + return err + } + apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage) // Establish the bidirectional communication, by creating a new UI backend and registering @@ -665,6 +695,13 @@ func signer(c *cli.Context) error { Service: api, Version: "1.0"}, } + + // + if pm != nil { + rpcAPI = addPluginAPIs(pm, rpcAPI, ui) + } + // + if c.GlobalBool(utils.HTTPEnabledFlag.Name) { vhosts := splitAndTrim(c.GlobalString(utils.HTTPVirtualHostsFlag.Name)) cors := splitAndTrim(c.GlobalString(utils.HTTPCORSDomainFlag.Name)) @@ -687,7 +724,7 @@ func signer(c *cli.Context) error { // start http server httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port) - httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) + httpServer, addr, _, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler, nil) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } @@ -736,6 +773,48 @@ func signer(c *cli.Context) error { return nil } +// +// startPluginManager gets plugin config and starts a new PluginManager +func startPluginManager(c *cli.Context) (*plugin.PluginManager, *plugin.Settings, error) { + nodeConf := new(node.Config) + if err := utils.SetPlugins(c, nodeConf); err != nil { + return nil, nil, err + } + pluginConf := nodeConf.Plugins + + if err := pluginConf.CheckSettingsAreSupported(supportedPlugins); err != nil { + return nil, nil, err + } + + pm, err := plugin.NewPluginManager("clef", pluginConf, c.Bool(utils.PluginSkipVerifyFlag.Name), c.Bool(utils.PluginLocalVerifyFlag.Name), c.String(utils.PluginPublicKeyFlag.Name)) + if err != nil { + return nil, nil, err + } + if err := pm.Start(nil); err != nil { + return nil, nil, err + } + return pm, pluginConf, nil +} + +// addPluginAPIs adds the exposed plugin APIs to Clef's API. +// It alters some of the plugin APIs so that calls to them will require UI approval. +func addPluginAPIs(pm *plugin.PluginManager, rpcAPI []rpc.API, ui core.UIClientAPI) []rpc.API { + // pm.APIs() returns a slice of values hence the following approach that may look clumsy + var ( + stdPluginAPIs = pm.APIs() + approvalPluginAPIs = make([]rpc.API, len(stdPluginAPIs)) + ) + + for i, api := range stdPluginAPIs { + switch s := api.Service.(type) { + case account.CreatorService: + api.Service = core.NewApprovalCreatorService(s, ui) + } + approvalPluginAPIs[i] = api + } + return append(rpcAPI, approvalPluginAPIs...) +} + // splitAndTrim splits input separated by a comma // and trims excessive white space from the substrings. func splitAndTrim(input string) []string { @@ -1186,3 +1265,53 @@ These data types are defined in the channel between clef and the UI`) fmt.Println(elem) } } + +// Quorum +// startClefAccountManagerWithPlugins - wrapped function to create a CLEF account manager with Plugin Support +func startClefAccountManagerWithPlugins(c *cli.Context, ksLocation string, nousb, lightKDF bool, scpath string) (*accounts.Manager, *plugin.PluginManager, error) { + var err error + // start the plugin manager + var ( + pm *plugin.PluginManager + pluginConf *plugin.Settings + ) + if c.IsSet(utils.PluginSettingsFlag.Name) { + log.Info("Using plugins") + pm, pluginConf, err = startPluginManager(c) + if err != nil { + utils.Fatalf(err.Error()) + } + + // setup goroutine to stop plugin manager when clef stops + go func() { + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(sigc) + <-sigc + log.Info("Got interrupt, shutting down...") + go pm.Stop() + for i := 10; i > 0; i-- { + <-sigc + if i > 1 { + log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) + } + } + debug.Exit() // ensure trace and CPU profile data is flushed. + debug.LoudPanic("boom") + }() + } + // + + am := core.StartClefAccountManager(ksLocation, nousb, lightKDF, pluginConf, scpath) + + // setup the pluggable accounts backend with the plugin + if pm != nil && pm.IsEnabled(plugin.AccountPluginInterfaceName) { + b := am.Backends(pluggable.BackendType)[0].(*pluggable.Backend) + if err := pm.AddAccountPluginToBackend(b); err != nil { + return nil, nil, err + } + } + // + + return am, pm, nil +} diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index a4fa971ebb..17cee09045 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -98,15 +98,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, return h } var ( - statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) - signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number)) - gaspool = new(core.GasPool) - blockHash = common.Hash{0x13, 0x37} - rejectedTxs []int - includedTxs types.Transactions - gasUsed = uint64(0) - receipts = make(types.Receipts, 0) - txIndex = 0 + statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) + privateStatedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) // Quorum private state db + signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number)) + gaspool = new(core.GasPool) + blockHash = common.Hash{0x13, 0x37} + rejectedTxs []int + includedTxs types.Transactions + gasUsed = uint64(0) + receipts = make(types.Receipts, 0) + txIndex = 0 ) gaspool.AddGas(pre.Env.GasLimit) vmContext := vm.Context{ @@ -145,7 +146,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmContext.GasPrice = msg.GasPrice() vmContext.Origin = msg.From() - evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) + evm := vm.NewEVM(vmContext, statedb, privateStatedb, chainConfig, vmConfig) snapshot := statedb.Snapshot() // (ret []byte, usedGas uint64, failed bool, err error) msgResult, err := core.ApplyMessage(evm, msg, gaspool) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 5a905c4f12..0bfb07a8cf 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -41,6 +41,8 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -504,7 +506,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { continue } // Submit the transaction and mark as funded if successful - if err := f.client.SendTransaction(context.Background(), signed); err != nil { + if err := f.client.SendTransaction(context.Background(), signed, bind.PrivateTxArgs{}); err != nil { f.lock.Unlock() if err = sendError(conn, err); err != nil { log.Warn("Failed to send transaction transmission error to client", "err", err) diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index 6d45c88763..6473a82744 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -187,6 +187,7 @@ this import mechanism is not needed when you transfer an account between nodes. `, }, + quorumAccountPluginCommands, }, } ) diff --git a/cmd/geth/accountcmd_plugin.go b/cmd/geth/accountcmd_plugin.go new file mode 100644 index 0000000000..aba700e7bf --- /dev/null +++ b/cmd/geth/accountcmd_plugin.go @@ -0,0 +1,323 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/pluggable" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/plugin" + "gopkg.in/urfave/cli.v1" +) + +var ( + quorumAccountPluginCommands = cli.Command{ + Name: "plugin", + Usage: "Manage 'account' plugin accounts", + Description: ` + geth account plugin + + Quorum supports alternate account management methods through the use of 'account' plugins. + + See docs.goquorum.com for more info. + `, + Subcommands: []cli.Command{ + { + Name: "list", + Usage: "Print summary of existing 'account' plugin accounts", + Action: utils.MigrateFlags(listPluginAccountsCLIAction), + Flags: []cli.Flag{ + utils.PluginSettingsFlag, // flag is used implicitly by makeConfigNode() + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.PluginSkipVerifyFlag, + }, + Description: ` + geth account plugin list +Print a short summary of all accounts for the given plugin settings`, + }, + { + Name: "new", + Usage: "Create a new account using an 'account' plugin", + Action: utils.MigrateFlags(createPluginAccountCLIAction), + Flags: []cli.Flag{ + utils.PluginSettingsFlag, + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.PluginSkipVerifyFlag, + utils.AccountPluginNewAccountConfigFlag, + }, + Description: fmt.Sprintf(` + geth account plugin new + +Creates a new account using an 'account' plugin and prints the address. + +--%v and --%v flags are required. + +Each 'account' plugin will have different requirements for the value of --%v. +For more info see the documentation for the particular 'account' plugin being used. +`, utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name), + }, + { + Name: "import", + Usage: "Import a private key into a new account using an 'account' plugin", + Action: utils.MigrateFlags(importPluginAccountCLIAction), + Flags: []cli.Flag{ + utils.PluginSettingsFlag, + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.PluginSkipVerifyFlag, + utils.AccountPluginNewAccountConfigFlag, + }, + ArgsUsage: "", + Description: ` + geth account plugin import + +Imports an unencrypted private key from and creates a new account using an 'account' plugin. +Prints the address. + +The keyfile must contain an unencrypted private key in hexadecimal format. + +--%v and --%v flags are required. + +Note: +Before using this import mechanism to transfer accounts that are already 'account' plugin-managed between nodes, consult +the documentation for the particular 'account' plugin being used as it may support alternate methods for transferring. +`, + }, + }, + } + + // supportedPlugins is the list of supported plugins for the account subcommand + supportedPlugins = []plugin.PluginInterfaceName{plugin.AccountPluginInterfaceName} + + invalidPluginFlagsErr = fmt.Errorf("--%v and --%v flags must be set", utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name) + + // makeConfigNodeDelegate is a wrapper for the makeConfigNode function. + // It can be replaced with a stub for testing. + makeConfigNodeDelegate configNodeMaker = standardConfigNodeMaker{} +) + +func listPluginAccountsCLIAction(ctx *cli.Context) error { + accts, err := listPluginAccounts(ctx) + if err != nil { + utils.Fatalf("%v", err) + } + + var index int + for _, acct := range accts { + fmt.Printf("Account #%d: {%x} %s\n", index, acct.Address, &acct.URL) + index++ + } + + return nil +} + +func listPluginAccounts(ctx *cli.Context) ([]accounts.Account, error) { + if !ctx.IsSet(utils.PluginSettingsFlag.Name) { + return []accounts.Account{}, fmt.Errorf("--%v required", utils.PluginSettingsFlag.Name) + } + + p, err := setupAccountPluginForCLI(ctx) + if err != nil { + return []accounts.Account{}, err + } + defer func() { + if err := p.teardown(); err != nil { + log.Error("error tearing down account plugin", "err", err) + } + }() + + return p.accounts(), nil +} + +func createPluginAccountCLIAction(ctx *cli.Context) error { + account, err := createPluginAccount(ctx) + if err != nil { + utils.Fatalf("unable to create plugin-backed account: %v", err) + } + writePluginAccountToStdOut(account) + return nil +} + +func createPluginAccount(ctx *cli.Context) (accounts.Account, error) { + if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) { + return accounts.Account{}, invalidPluginFlagsErr + } + + newAcctCfg, err := getNewAccountConfigFromCLI(ctx) + if err != nil { + return accounts.Account{}, err + } + + p, err := setupAccountPluginForCLI(ctx) + if err != nil { + return accounts.Account{}, err + } + defer func() { + if err := p.teardown(); err != nil { + log.Error("error tearing down account plugin", "err", err) + } + }() + + return p.NewAccount(newAcctCfg) +} + +func importPluginAccountCLIAction(ctx *cli.Context) error { + account, err := importPluginAccount(ctx) + if err != nil { + utils.Fatalf("unable to import key and create plugin-backed account: %v", err) + } + writePluginAccountToStdOut(account) + return nil +} + +func importPluginAccount(ctx *cli.Context) (accounts.Account, error) { + keyfile := ctx.Args().First() + if len(keyfile) == 0 { + return accounts.Account{}, errors.New("keyfile must be given as argument") + } + key, err := crypto.LoadECDSA(keyfile) + if err != nil { + return accounts.Account{}, fmt.Errorf("Failed to load the private key: %v", err) + } + keyBytes := crypto.FromECDSA(key) + keyHex := hex.EncodeToString(keyBytes) + + if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) { + return accounts.Account{}, invalidPluginFlagsErr + } + + newAcctCfg, err := getNewAccountConfigFromCLI(ctx) + if err != nil { + return accounts.Account{}, err + } + + p, err := setupAccountPluginForCLI(ctx) + if err != nil { + return accounts.Account{}, err + } + defer func() { + if err := p.teardown(); err != nil { + log.Error("error tearing down account plugin", "err", err) + } + }() + + return p.ImportRawKey(keyHex, newAcctCfg) +} + +func getNewAccountConfigFromCLI(ctx *cli.Context) (map[string]interface{}, error) { + data := ctx.String(utils.AccountPluginNewAccountConfigFlag.Name) + conf, err := plugin.ReadMultiFormatConfig(data) + if err != nil { + return nil, fmt.Errorf("invalid account creation config provided: %v", err) + } + // plugin backend expects config to be json map + confMap := new(map[string]interface{}) + if err := json.Unmarshal(conf, confMap); err != nil { + return nil, fmt.Errorf("invalid account creation config provided: %v", err) + } + return *confMap, nil +} + +type accountPlugin struct { + pluggable.AccountCreator + am *accounts.Manager + pm *plugin.PluginManager +} + +func (c *accountPlugin) teardown() error { + return c.pm.Stop() +} + +func (c *accountPlugin) accounts() []accounts.Account { + b := c.am.Backends(pluggable.BackendType) + if b == nil { + return []accounts.Account{} + } + + var accts []accounts.Account + for _, wallet := range b[0].Wallets() { + accts = append(accts, wallet.Accounts()...) + } + return accts +} + +// startPluginManagerForAccountCLI is a helper func for use with the account plugin CLI. +// It creates and starts a new PluginManager with the provided CLI flags. +// The caller should call teardown on the returned accountPlugin to stop the plugin after use. +// The returned accountPlugin provides several methods necessary for the account plugin CLI, abstracting the underlying plugin/account types. +// +// This func should not be used for anything other than the account CLI. +// The account plugin, if present, is registered with the existing pluggable.Backend in the stack's AccountManager. +// This allows the AccountManager to use the account plugin even though the PluginManager is not registered with the stack. +// Instead of registering a plugin manager with the stack this is manually creating a plugin manager. +// This means that the plugin manager can be started without having to start the whole stack (P2P client, IPC interface, ...). +// The purpose of this is to help prevent issues/conflicts if an existing node is already running on this host. +// +func setupAccountPluginForCLI(ctx *cli.Context) (*accountPlugin, error) { + stack, cfg := makeConfigNodeDelegate.makeConfigNode(ctx) + + if cfg.Node.Plugins == nil { + return nil, errors.New("no plugin config provided") + } + if err := cfg.Node.Plugins.CheckSettingsAreSupported(supportedPlugins); err != nil { + return nil, err + } + if err := cfg.Node.ResolvePluginBaseDir(); err != nil { + return nil, fmt.Errorf("unable to resolve plugin base dir due to %s", err) + } + + pm, err := plugin.NewPluginManager( + cfg.Node.UserIdent, + cfg.Node.Plugins, + ctx.Bool(utils.PluginSkipVerifyFlag.Name), + ctx.Bool(utils.PluginLocalVerifyFlag.Name), + ctx.String(utils.PluginPublicKeyFlag.Name), + ) + if err != nil { + return nil, fmt.Errorf("unable to create plugin manager: %v", err) + } + if err := pm.Start(nil); err != nil { + return nil, fmt.Errorf("unable to start plugin manager: %v", err) + } + + b := stack.AccountManager().Backends(pluggable.BackendType)[0].(*pluggable.Backend) + if err := pm.AddAccountPluginToBackend(b); err != nil { + return nil, fmt.Errorf("unable to load pluggable account backend: %v", err) + } + + return &accountPlugin{ + AccountCreator: b, + am: stack.AccountManager(), + pm: pm, + }, nil +} + +func writePluginAccountToStdOut(account accounts.Account) { + fmt.Printf("\nYour new plugin-backed account was generated\n\n") + fmt.Printf("Public address of the account: %s\n", account.Address.Hex()) + fmt.Printf("Account URL: %s\n\n", account.URL.Path) + fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") + fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") + fmt.Printf("- Consider BACKING UP your account! The specifics of backing up will depend on the plugin backend being used.\n") + fmt.Printf("- The plugin backend may require you to REMEMBER part/all of the new account config to retrieve the key in the future!\n See the plugin specific documentation for more info.\n\n") + fmt.Printf("- See the documentation for the plugin being used for more info.\n\n") +} + +type configNodeMaker interface { + makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) +} + +// standardConfigNodeMaker is a wrapper around the makeConfigNode function to enable mocking in testing +type standardConfigNodeMaker struct{} + +func (f standardConfigNodeMaker) makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { + return makeConfigNode(ctx) +} diff --git a/cmd/geth/accountcmd_plugin_test.go b/cmd/geth/accountcmd_plugin_test.go new file mode 100644 index 0000000000..6967be360e --- /dev/null +++ b/cmd/geth/accountcmd_plugin_test.go @@ -0,0 +1,299 @@ +package main + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/plugin" + "github.com/stretchr/testify/require" + "gopkg.in/urfave/cli.v1" +) + +// newAccountPluginCLIContext creates a cli.Context setup with the core account plugin CLI flags. +// args sets the values of the flags. +func newAccountPluginCLIContext(args []string) *cli.Context { + fs := &flag.FlagSet{} + fs.String(utils.PluginSettingsFlag.Name, "", "") + fs.String(utils.AccountPluginNewAccountConfigFlag.Name, "", "") + _ = fs.Parse(args) + + return cli.NewContext(nil, fs, nil) +} + +type mockConfigNodeMaker struct { + do func(ctx *cli.Context) (*node.Node, gethConfig) +} + +func (m *mockConfigNodeMaker) makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { + return m.do(ctx) +} + +func TestListPluginAccounts_ErrIfCLIFlagNotSet(t *testing.T) { + var args []string + ctx := newAccountPluginCLIContext(args) + + _, err := listPluginAccounts(ctx) + require.EqualError(t, err, "--plugins required") +} + +func TestListPluginAccounts_ErrIfUnsupportedPluginInConfig(t *testing.T) { + var unsupportedPlugin plugin.PluginInterfaceName = "somename" + pluginSettings := plugin.Settings{ + Providers: map[plugin.PluginInterfaceName]plugin.PluginDefinition{ + unsupportedPlugin: {}, + }, + } + + args := []string{ + "--plugins", "/path/to/config.json", + } + ctx := newAccountPluginCLIContext(args) + + makeConfigNodeDelegate = &mockConfigNodeMaker{ + do: func(ctx *cli.Context) (*node.Node, gethConfig) { + return nil, gethConfig{ + Node: node.Config{ + Plugins: &pluginSettings, + }, + } + }, + } + + _, err := listPluginAccounts(ctx) + require.EqualError(t, err, "unsupported plugins configured: [somename]") +} + +func TestCreatePluginAccount_ErrIfCLIFlagsNotSet(t *testing.T) { + tests := []struct { + name string + args []string + }{ + { + name: "no plugin flags", + args: []string{}, + }, + { + name: "only plugin settings flag", + args: []string{"--plugins", "/path/to/config.json"}, + }, + { + name: "only new plugin account config settings flag", + args: []string{"--plugins.account.config", "/path/to/new-acct-config.json"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := newAccountPluginCLIContext(tt.args) + + _, err := createPluginAccount(ctx) + require.EqualError(t, err, "--plugins and --plugins.account.config flags must be set") + }) + } +} + +func TestCreatePluginAccount_ErrIfInvalidNewAccountConfig(t *testing.T) { + tests := []struct { + name string + flagValue string + wantErrMsg string + }{ + { + name: "json: invalid json", + flagValue: "{invalidjson: abc}", + wantErrMsg: "invalid account creation config provided: invalid character 'i' looking for beginning of object key string", + }, + { + name: "file: does not exist", + flagValue: "file://doesnotexist", + wantErrMsg: "invalid account creation config provided: open doesnotexist: no such file or directory", + }, + { + name: "env: not set", + flagValue: "env://notset", + wantErrMsg: "invalid account creation config provided: env variable notset not found", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := []string{ + "--plugins", "/path/to/config.json", + "--plugins.account.config", tt.flagValue, + } + ctx := newAccountPluginCLIContext(args) + _, err := createPluginAccount(ctx) + require.EqualError(t, err, tt.wantErrMsg) + }) + } +} + +func TestCreatePluginAccount_ErrIfUnsupportedPluginInConfig(t *testing.T) { + var unsupportedPlugin plugin.PluginInterfaceName = "somename" + pluginSettings := plugin.Settings{ + Providers: map[plugin.PluginInterfaceName]plugin.PluginDefinition{ + unsupportedPlugin: {}, + }, + } + + args := []string{ + "--plugins", "/path/to/config.json", + "--plugins.account.config", "{}", + } + ctx := newAccountPluginCLIContext(args) + + makeConfigNodeDelegate = &mockConfigNodeMaker{ + do: func(ctx *cli.Context) (*node.Node, gethConfig) { + return nil, gethConfig{ + Node: node.Config{ + Plugins: &pluginSettings, + }, + } + }, + } + + _, err := createPluginAccount(ctx) + require.EqualError(t, err, "unsupported plugins configured: [somename]") +} + +func TestImportPluginAccount_ErrIfNoArg(t *testing.T) { + var args []string + ctx := newAccountPluginCLIContext(args) + + _, err := importPluginAccount(ctx) + require.EqualError(t, err, "keyfile must be given as argument") +} + +func TestImportPluginAccount_ErrIfInvalidRawkey(t *testing.T) { + args := []string{"/incorrect/path/to/file.key"} + ctx := newAccountPluginCLIContext(args) + + _, err := importPluginAccount(ctx) + require.EqualError(t, err, "Failed to load the private key: open /incorrect/path/to/file.key: no such file or directory") +} + +func TestImportPluginAccount_ErrIfCLIFlagsNotSet(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "rawkey") + require.NoError(t, err) + t.Log("creating tmp file", "path", tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + _, err = tmpfile.Write([]byte("1fe8f1ad4053326db20529257ac9401f2e6c769ef1d736b8c2f5aba5f787c72b")) + require.NoError(t, err) + err = tmpfile.Close() + require.NoError(t, err) + + tests := []struct { + name string + args []string + }{ + { + name: "no plugin flags", + args: []string{tmpfile.Name()}, + }, + { + name: "only plugin settings flag", + args: []string{"--plugins", "/path/to/config.json", tmpfile.Name()}, + }, + { + name: "only new plugin account config settings flag", + args: []string{"--plugins.account.config", "/path/to/new-acct-config.json", tmpfile.Name()}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := newAccountPluginCLIContext(tt.args) + + _, err := importPluginAccount(ctx) + require.EqualError(t, err, "--plugins and --plugins.account.config flags must be set") + }) + } +} + +func TestImportPluginAccount_ErrIfInvalidNewAccountConfig(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "rawkey") + require.NoError(t, err) + t.Log("creating tmp file", "path", tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + _, err = tmpfile.Write([]byte("1fe8f1ad4053326db20529257ac9401f2e6c769ef1d736b8c2f5aba5f787c72b")) + require.NoError(t, err) + err = tmpfile.Close() + require.NoError(t, err) + + tests := []struct { + name string + flagValue string + wantErrMsg string + }{ + { + name: "json: invalid json", + flagValue: "{invalidjson: abc}", + wantErrMsg: "invalid account creation config provided: invalid character 'i' looking for beginning of object key string", + }, + { + name: "file: does not exist", + flagValue: "file://doesnotexist", + wantErrMsg: "invalid account creation config provided: open doesnotexist: no such file or directory", + }, + { + name: "env: not set", + flagValue: "env://notset", + wantErrMsg: "invalid account creation config provided: env variable notset not found", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := []string{ + "--plugins", "/path/to/config.json", + "--plugins.account.config", tt.flagValue, + tmpfile.Name(), + } + ctx := newAccountPluginCLIContext(args) + _, err := importPluginAccount(ctx) + require.EqualError(t, err, tt.wantErrMsg) + }) + } +} + +func TestImportPluginAccount_ErrIfUnsupportedPluginInConfig(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "rawkey") + require.NoError(t, err) + t.Log("creating tmp file", "path", tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + _, err = tmpfile.Write([]byte("1fe8f1ad4053326db20529257ac9401f2e6c769ef1d736b8c2f5aba5f787c72b")) + require.NoError(t, err) + err = tmpfile.Close() + require.NoError(t, err) + + var unsupportedPlugin plugin.PluginInterfaceName = "somename" + pluginSettings := plugin.Settings{ + Providers: map[plugin.PluginInterfaceName]plugin.PluginDefinition{ + unsupportedPlugin: {}, + }, + } + + args := []string{ + "--plugins", "/path/to/config.json", + "--plugins.account.config", "{}", + tmpfile.Name(), + } + ctx := newAccountPluginCLIContext(args) + + makeConfigNodeDelegate = &mockConfigNodeMaker{ + do: func(ctx *cli.Context) (*node.Node, gethConfig) { + return nil, gethConfig{ + Node: node.Config{ + Plugins: &pluginSettings, + }, + } + }, + } + + _, err = importPluginAccount(ctx) + require.EqualError(t, err, "unsupported plugins configured: [somename]") +} diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 6213e5195d..32070e11bf 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -39,9 +39,20 @@ func tmpDatadirWithKeystore(t *testing.T) string { if err := cp.CopyAll(keystore, source); err != nil { t.Fatal(err) } + // add the necessary files for geth to start with the raft consensus + geth := filepath.Join(datadir, "geth") + sourceNodeKey := filepath.Join("testdata", "geth") + if err := cp.CopyAll(geth, sourceNodeKey); err != nil { + t.Fatal(err) + } return datadir } +func runGethWithRaftConsensus(t *testing.T, args ...string) *testgeth { + argsWithRaft := append([]string{"--raft"}, args...) + return runGeth(t, argsWithRaft...) +} + func TestAccountListEmpty(t *testing.T) { geth := runGeth(t, "account", "list") geth.ExpectExit() @@ -178,8 +189,9 @@ Fatal: could not decrypt key with given password } func TestUnlockFlag(t *testing.T) { + defer SetResetPrivateConfig("ignore")() datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, + geth := runGethWithRaftConsensus(t, "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") @@ -201,7 +213,31 @@ Password: {{.InputLine "foobar"}} } } +func TestGethDoesntStartWithoutConfiguredConsensus(t *testing.T) { + defer SetResetPrivateConfig("ignore")() + + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0") + + expectedText := "Consensus not specified. Exiting!!" + + // changed to expect regexp because fatalf writes the message to stdout/stderr + geth.ExpectRegexp(expectedText) +} + +func TestGethStartsWhenConsensusAndPrivateConfigAreConfigured(t *testing.T) { + defer SetResetPrivateConfig("ignore")() + + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--raft") + + geth.ExpectExit() +} + func TestUnlockFlagWrongPassword(t *testing.T) { + defer SetResetPrivateConfig("ignore")() datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", @@ -221,8 +257,9 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could // https://github.com/ethereum/go-ethereum/issues/1785 func TestUnlockFlagMultiIndex(t *testing.T) { + defer SetResetPrivateConfig("ignore")() datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, + geth := runGethWithRaftConsensus(t, "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--unlock", "0,2", "js", "testdata/empty.js") @@ -248,8 +285,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagPasswordFile(t *testing.T) { + defer SetResetPrivateConfig("ignore")() datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, + geth := runGethWithRaftConsensus(t, "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--password", "testdata/passwords.txt", "--unlock", "0,2", "js", "testdata/empty.js") @@ -268,6 +306,7 @@ func TestUnlockFlagPasswordFile(t *testing.T) { } func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { + defer SetResetPrivateConfig("ignore")() datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", @@ -279,9 +318,11 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) } func TestUnlockFlagAmbiguous(t *testing.T) { + defer SetResetPrivateConfig("ignore")() + datadir := tmpDatadirWithKeystore(t) store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", + geth := runGethWithRaftConsensus(t, + "--datadir", datadir, "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") defer geth.ExpectExit() @@ -317,6 +358,7 @@ In order to avoid this warning, you need to remove the following duplicate key f } func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { + defer SetResetPrivateConfig("ignore")() store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runGeth(t, "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 247c202bca..dc1039ab70 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -19,6 +19,7 @@ package main import ( "encoding/json" "fmt" + "io" "os" "path/filepath" "runtime" @@ -133,7 +134,7 @@ be gzipped.`, }, Category: "BLOCKCHAIN COMMANDS", Description: ` - The import-preimages command imports hash preimages from an RLP encoded stream.`, +The import-preimages command imports hash preimages from an RLP encoded stream.`, } exportPreimagesCommand = cli.Command{ Action: utils.MigrateFlags(exportPreimages), @@ -221,6 +222,24 @@ Use "ethereum dump 0" to dump the genesis block.`, } ) +// In the regular Genesis / ChainConfig struct, due to the way go deserializes +// json, IsQuorum defaults to false (when not specified). Here we specify it as +// a pointer so we can make the distinction and default unspecified to true. +func getIsQuorum(file io.Reader) bool { + altGenesis := new(struct { + Config *struct { + IsQuorum *bool `json:"isQuorum"` + } `json:"config"` + }) + + if err := json.NewDecoder(file).Decode(altGenesis); err != nil { + utils.Fatalf("invalid genesis file: %v", err) + } + + // unspecified defaults to true + return altGenesis.Config.IsQuorum == nil || *altGenesis.Config.IsQuorum +} + // initGenesis will initialise the given JSON format genesis file and writes it as // the zero'd block (i.e. genesis) or will fail hard if it can't succeed. func initGenesis(ctx *cli.Context) error { @@ -239,6 +258,17 @@ func initGenesis(ctx *cli.Context) error { if err := json.NewDecoder(file).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } + + file.Seek(0, 0) + genesis.Config.IsQuorum = getIsQuorum(file) + + // check the data given as a part of newMaxConfigData to ensure that + // its in expected order + err = genesis.Config.CheckMaxCodeConfigData() + if err != nil { + utils.Fatalf("maxCodeSize data invalid: %v", err) + } + // Open an initialise both full and light databases stack := makeFullNode(ctx) defer stack.Close() @@ -280,7 +310,7 @@ func importChain(ctx *cli.Context) error { stack := makeFullNode(ctx) defer stack.Close() - chain, db := utils.MakeChain(ctx, stack, false) + chain, db := utils.MakeChain(ctx, stack, false, true) defer db.Close() // Start periodically gathering memory profiles @@ -374,7 +404,7 @@ func exportChain(ctx *cli.Context) error { stack := makeFullNode(ctx) defer stack.Close() - chain, _ := utils.MakeChain(ctx, stack, true) + chain, _ := utils.MakeChain(ctx, stack, true, true) start := time.Now() var err error @@ -449,7 +479,7 @@ func copyDb(ctx *cli.Context) error { stack := makeFullNode(ctx) defer stack.Close() - chain, chainDb := utils.MakeChain(ctx, stack, false) + chain, chainDb := utils.MakeChain(ctx, stack, false, false) syncMode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode) var syncBloom *trie.SyncBloom @@ -557,7 +587,7 @@ func dump(ctx *cli.Context) error { stack := makeFullNode(ctx) defer stack.Close() - chain, chainDb := utils.MakeChain(ctx, stack, true) + chain, chainDb := utils.MakeChain(ctx, stack, true, false) defer chainDb.Close() for _, arg := range ctx.Args() { var block *types.Block @@ -596,7 +626,7 @@ func inspect(ctx *cli.Context) error { node, _ := makeConfigNode(ctx) defer node.Close() - _, chainDb := utils.MakeChain(ctx, node, true) + _, chainDb := utils.MakeChain(ctx, node, true, false) defer chainDb.Close() return rawdb.InspectDatabase(chainDb) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 01ca0a9cfb..5ba1dc9cab 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -24,14 +24,17 @@ import ( "reflect" "unicode" - cli "gopkg.in/urfave/cli.v1" - "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common/http" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" "github.com/naoina/toml" + "gopkg.in/urfave/cli.v1" ) var ( @@ -146,7 +149,25 @@ func enableWhisper(ctx *cli.Context) bool { func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) - utils.RegisterEthService(stack, &cfg.Eth) + ethChan := utils.RegisterEthService(stack, &cfg.Eth) + + // plugin service must be after eth service so that eth service will be stopped gradually if any of the plugin + // fails to start + if cfg.Node.Plugins != nil { + utils.RegisterPluginService(stack, &cfg.Node, ctx.Bool(utils.PluginSkipVerifyFlag.Name), ctx.Bool(utils.PluginLocalVerifyFlag.Name), ctx.String(utils.PluginPublicKeyFlag.Name)) + } + + if cfg.Node.IsPermissionEnabled() { + utils.RegisterPermissionService(stack, ctx.Bool(utils.RaftDNSEnabledFlag.Name)) + } + + if ctx.GlobalBool(utils.RaftModeFlag.Name) { + utils.RegisterRaftService(stack, ctx, &cfg.Node, ethChan) + } + + if private.IsQuorumPrivacyEnabled() { + utils.RegisterExtensionService(stack, ethChan) + } // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode shhEnabled := enableWhisper(ctx) @@ -202,3 +223,88 @@ func dumpConfig(ctx *cli.Context) error { return nil } + +// quorumValidateEthService checks quorum features that depend on the ethereum service +func quorumValidateEthService(stack *node.Node, isRaft bool) { + var ethereum *eth.Ethereum + + err := stack.Service(ðereum) + if err != nil { + utils.Fatalf("Error retrieving Ethereum service: %v", err) + } + + quorumValidateConsensus(ethereum, isRaft) + + quorumValidatePrivacyEnhancements(ethereum) +} + +// quorumValidateConsensus checks if a consensus was used. The node is killed if consensus was not used +func quorumValidateConsensus(ethereum *eth.Ethereum, isRaft bool) { + if !isRaft && ethereum.BlockChain().Config().Istanbul == nil && ethereum.BlockChain().Config().Clique == nil { + utils.Fatalf("Consensus not specified. Exiting!!") + } +} + +// quorumValidatePrivacyEnhancements checks if privacy enhancements are configured the transaction manager supports +// the PrivacyEnhancements feature +func quorumValidatePrivacyEnhancements(ethereum *eth.Ethereum) { + privacyEnhancementsBlock := ethereum.BlockChain().Config().PrivacyEnhancementsBlock + if privacyEnhancementsBlock != nil { + log.Info("Privacy enhancements is configured to be enabled from block ", "height", privacyEnhancementsBlock) + if !private.P.HasFeature(engine.PrivacyEnhancements) { + utils.Fatalf("Cannot start quorum with privacy enhancements enabled while the transaction manager does not support it") + } + } +} + +// Get private transaction manager configuration +func QuorumSetupPrivacyConfiguration(ctx *cli.Context) (http.Config, error) { + // get default configuration + cfg, err := private.GetLegacyEnvironmentConfig() + if err != nil { + return http.Config{}, err + } + + // override the config with command line parameters + if ctx.GlobalIsSet(utils.QuorumPTMUnixSocketFlag.Name) { + cfg.SetSocket(ctx.GlobalString(utils.QuorumPTMUnixSocketFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMUrlFlag.Name) { + cfg.SetHttpUrl(ctx.GlobalString(utils.QuorumPTMUrlFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMTimeoutFlag.Name) { + cfg.SetTimeout(ctx.GlobalUint(utils.QuorumPTMTimeoutFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMDialTimeoutFlag.Name) { + cfg.SetDialTimeout(ctx.GlobalUint(utils.QuorumPTMDialTimeoutFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMHttpIdleTimeoutFlag.Name) { + cfg.SetHttpIdleConnTimeout(ctx.GlobalUint(utils.QuorumPTMHttpIdleTimeoutFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMHttpWriteBufferSizeFlag.Name) { + cfg.SetHttpWriteBufferSize(ctx.GlobalInt(utils.QuorumPTMHttpWriteBufferSizeFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMHttpReadBufferSizeFlag.Name) { + cfg.SetHttpReadBufferSize(ctx.GlobalInt(utils.QuorumPTMHttpReadBufferSizeFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMTlsModeFlag.Name) { + cfg.SetTlsMode(ctx.GlobalString(utils.QuorumPTMTlsModeFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMTlsRootCaFlag.Name) { + cfg.SetTlsRootCA(ctx.GlobalString(utils.QuorumPTMTlsRootCaFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMTlsClientCertFlag.Name) { + cfg.SetTlsClientCert(ctx.GlobalString(utils.QuorumPTMTlsClientCertFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMTlsClientKeyFlag.Name) { + cfg.SetTlsClientKey(ctx.GlobalString(utils.QuorumPTMTlsClientKeyFlag.Name)) + } + if ctx.GlobalIsSet(utils.QuorumPTMTlsInsecureSkipVerify.Name) { + cfg.SetTlsInsecureSkipVerify(ctx.Bool(utils.QuorumPTMTlsInsecureSkipVerify.Name)) + } + + if err = cfg.Validate(); err != nil { + return cfg, err + } + return cfg, nil +} diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 2a304636eb..c6d0b6caa7 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -17,7 +17,13 @@ package main import ( + "context" + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" + "net/http" + "net/url" "os" "os/signal" "path/filepath" @@ -26,13 +32,16 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/plugin/security" "github.com/ethereum/go-ethereum/rpc" "gopkg.in/urfave/cli.v1" ) var ( - consoleFlags = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag} + consoleFlags = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag} + rpcClientFlags = []cli.Flag{utils.RPCClientToken, utils.RPCClientTLSCert, utils.RPCClientTLSCaCert, utils.RPCClientTLSCipherSuites, utils.RPCClientTLSInsecureSkipVerify} consoleCommand = cli.Command{ Action: utils.MigrateFlags(localConsole), @@ -51,7 +60,7 @@ See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, Name: "attach", Usage: "Start an interactive JavaScript environment (connect to node)", ArgsUsage: "[endpoint]", - Flags: append(consoleFlags, utils.DataDirFlag), + Flags: append(append(consoleFlags, utils.DataDirFlag), rpcClientFlags...), Category: "CONSOLE COMMANDS", Description: ` The Geth console is an interactive shell for the JavaScript runtime environment @@ -73,6 +82,74 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons } ) +// Quorum +// +// read tls client configuration from command line arguments +// +// only for HTTPS or WSS +func readTLSClientConfig(endpoint string, ctx *cli.Context) (*tls.Config, bool, error) { + if !strings.HasPrefix(endpoint, "https://") && !strings.HasPrefix(endpoint, "wss://") { + return nil, false, nil + } + hasCustomTls := false + insecureSkipVerify := ctx.Bool(utils.RPCClientTLSInsecureSkipVerify.Name) + tlsConfig := &tls.Config{ + InsecureSkipVerify: insecureSkipVerify, + } + var certFile, caFile string + if !insecureSkipVerify { + var certPem, caPem []byte + certFile, caFile = ctx.String(utils.RPCClientTLSCert.Name), ctx.String(utils.RPCClientTLSCaCert.Name) + var err error + if certFile != "" { + if certPem, err = ioutil.ReadFile(certFile); err != nil { + return nil, true, err + } + } + if caFile != "" { + if caPem, err = ioutil.ReadFile(caFile); err != nil { + return nil, true, err + } + } + if len(certPem) != 0 || len(caPem) != 0 { + certPool, err := x509.SystemCertPool() + if err != nil { + certPool = x509.NewCertPool() + } + if len(certPem) != 0 { + certPool.AppendCertsFromPEM(certPem) + } + if len(caPem) != 0 { + certPool.AppendCertsFromPEM(caPem) + } + tlsConfig.RootCAs = certPool + hasCustomTls = true + } + } else { + hasCustomTls = true + } + cipherSuitesInput := ctx.String(utils.RPCClientTLSCipherSuites.Name) + cipherSuitesStrings := strings.FieldsFunc(cipherSuitesInput, func(r rune) bool { + return r == ',' + }) + if len(cipherSuitesStrings) > 0 { + cipherSuiteList := make(security.CipherSuiteList, len(cipherSuitesStrings)) + for i, s := range cipherSuitesStrings { + cipherSuiteList[i] = security.CipherSuite(strings.TrimSpace(s)) + } + cipherSuites, err := cipherSuiteList.ToUint16Array() + if err != nil { + return nil, true, err + } + tlsConfig.CipherSuites = cipherSuites + hasCustomTls = true + } + if !hasCustomTls { + return nil, false, nil + } + return tlsConfig, hasCustomTls, nil +} + // localConsole starts a new geth node, attaching a JavaScript console to it at the // same time. func localConsole(ctx *cli.Context) error { @@ -142,7 +219,7 @@ func remoteConsole(ctx *cli.Context) error { } endpoint = fmt.Sprintf("%s/geth.ipc", path) } - client, err := dialRPC(endpoint) + client, err := dialRPC(endpoint, ctx) if err != nil { utils.Fatalf("Unable to attach to remote geth: %v", err) } @@ -153,20 +230,20 @@ func remoteConsole(ctx *cli.Context) error { Preload: utils.MakeConsolePreloads(ctx), } - console, err := console.New(config) + consl, err := console.New(config) if err != nil { utils.Fatalf("Failed to start the JavaScript console: %v", err) } - defer console.Stop(false) + defer consl.Stop(false) if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { - console.Evaluate(script) + consl.Evaluate(script) return nil } // Otherwise print the welcome screen and enter interactive mode - console.Welcome() - console.Interactive() + consl.Welcome() + consl.Interactive() return nil } @@ -174,7 +251,11 @@ func remoteConsole(ctx *cli.Context) error { // dialRPC returns a RPC client which connects to the given endpoint. // The check for empty endpoint implements the defaulting logic // for "geth attach" and "geth monitor" with no argument. -func dialRPC(endpoint string) (*rpc.Client, error) { +// +// Quorum: passing the cli context to build security-aware client: +// 1. Custom TLS configuration +// 2. Access Token awareness via rpc.HttpCredentialsProviderFunc +func dialRPC(endpoint string, ctx *cli.Context) (*rpc.Client, error) { if endpoint == "" { endpoint = node.DefaultIPCEndpoint(clientIdentifier) } else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { @@ -182,7 +263,47 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // these prefixes. endpoint = endpoint[4:] } - return rpc.Dial(endpoint) + var ( + client *rpc.Client + err error + dialCtx = context.Background() + ) + tlsConfig, hasCustomTls, tlsReadErr := readTLSClientConfig(endpoint, ctx) + if tlsReadErr != nil { + return nil, tlsReadErr + } + if token := ctx.String(utils.RPCClientToken.Name); token != "" { + var f rpc.HttpCredentialsProviderFunc = func(ctx context.Context) (string, error) { + return token, nil + } + // it's important that f MUST BE OF TYPE rpc.HttpCredentialsProviderFunc + dialCtx = context.WithValue(dialCtx, rpc.CtxCredentialsProvider, f) + } + if hasCustomTls { + u, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + switch u.Scheme { + case "https": + customHttpClient := &http.Client{ + Transport: http.DefaultTransport, + } + customHttpClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig + client, _ = rpc.DialHTTPWithClient(endpoint, customHttpClient) + case "wss": + client, _ = rpc.DialWebsocketWithCustomTLS(dialCtx, endpoint, "", tlsConfig) + default: + log.Warn("unsupported scheme for custom TLS which is only for HTTPS/WSS", "scheme", u.Scheme) + client, _ = rpc.DialContext(dialCtx, endpoint) + } + } else { + client, err = rpc.DialContext(dialCtx, endpoint) + } + if f, ok := dialCtx.Value(rpc.CtxCredentialsProvider).(rpc.HttpCredentialsProviderFunc); ok && err == nil { + client, err = client.WithHTTPCredentials(f) + } + return client, err } // ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index a2489892e4..436ea34fc6 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -18,6 +18,9 @@ package main import ( "crypto/rand" + "crypto/tls" + "flag" + "io/ioutil" "math/big" "os" "path/filepath" @@ -27,22 +30,60 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/params" + testifyassert "github.com/stretchr/testify/assert" + "gopkg.in/urfave/cli.v1" ) const ( - ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0" - httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" + ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0" + httpAPIs = "admin:1.0 eth:1.0 net:1.0 rpc:1.0 web3:1.0" + nodeKey = "b68c0338aa4b266bf38ebe84c6199ae9fac8b29f32998b3ed2fbeafebe8d65c9" ) +var genesis = `{ + "config": { + "chainId": 2017, + "homesteadBlock": 1, + "eip150Block": 2, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 3, + "eip158Block": 3, + "istanbul": { + "epoch": 30000, + "policy": 0 + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "gasLimit": "0x47b760", + "difficulty": "0x1", + "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "491937757d1b26e29c507b8d4c0b233c2747e68d": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} +` + // Tests that a node embedded within a console can be started up properly and // then terminated by closing the input stream. func TestConsoleWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" + defer SetResetPrivateConfig("ignore")() + coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d" + + datadir := setupIstanbul(t) + defer os.RemoveAll(datadir) // Start a geth console, make sure it's cleaned up and terminate the console geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", + "--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--etherbase", coinbase, "--shh", "console") @@ -50,7 +91,8 @@ func TestConsoleWelcome(t *testing.T) { geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH }) geth.SetTemplateFunc("gover", runtime.Version) - geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") }) + geth.SetTemplateFunc("gethver", func() string { return params.VersionWithMeta }) + geth.SetTemplateFunc("quorumver", func() string { return params.QuorumVersion }) geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)") }) @@ -60,7 +102,7 @@ func TestConsoleWelcome(t *testing.T) { geth.Expect(` Welcome to the Geth JavaScript console! -instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}} +instance: Geth/v{{gethver}}(quorum-v{{quorumver}})/{{goos}}-{{goarch}}/{{gover}} coinbase: {{.Etherbase}} at block: 0 ({{niltime}}) datadir: {{.Datadir}} @@ -73,20 +115,24 @@ at block: 0 ({{niltime}}) // Tests that a console can be attached to a running node via various means. func TestIPCAttachWelcome(t *testing.T) { + defer SetResetPrivateConfig("ignore")() // Configure the instance for IPC attachment - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" + coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d" var ipc string + + datadir := setupIstanbul(t) + defer os.RemoveAll(datadir) + if runtime.GOOS == "windows" { ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999)) } else { - ws := tmpdir(t) - defer os.RemoveAll(ws) - ipc = filepath.Join(ws, "geth.ipc") + ipc = filepath.Join(datadir, "geth.ipc") } + // Note: we need --shh because testAttachWelcome checks for default // list of ipc modules and shh is included there. geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", + "--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--etherbase", coinbase, "--shh", "--ipcpath", ipc) defer func() { @@ -100,15 +146,16 @@ func TestIPCAttachWelcome(t *testing.T) { } func TestHTTPAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" + defer SetResetPrivateConfig("ignore")() + coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d" port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P + + datadir := setupIstanbul(t) + defer os.RemoveAll(datadir) + geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--http", "--http.port", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() + "--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", + "--etherbase", coinbase, "--http", "--http.port", port, "--rpcapi", "admin,eth,net,web3") endpoint := "http://127.0.0.1:" + port waitForEndpoint(t, endpoint, 3*time.Second) @@ -116,16 +163,16 @@ func TestHTTPAttachWelcome(t *testing.T) { } func TestWSAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" + defer SetResetPrivateConfig("ignore")() + coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d" port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P + datadir := setupIstanbul(t) + defer os.RemoveAll(datadir) + geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ws", "--ws.port", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() + "--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", + "--etherbase", coinbase, "--ws", "--ws.port", port, "--wsapi", "admin,eth,net,web3") endpoint := "ws://127.0.0.1:" + port waitForEndpoint(t, endpoint, 3*time.Second) @@ -142,12 +189,13 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { attach.SetTemplateFunc("goos", func() string { return runtime.GOOS }) attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH }) attach.SetTemplateFunc("gover", runtime.Version) - attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") }) + attach.SetTemplateFunc("gethver", func() string { return params.VersionWithMeta }) + attach.SetTemplateFunc("quorumver", func() string { return params.QuorumVersion }) attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase }) attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)") }) - attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") }) + attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") || strings.Contains(apis, "admin") }) attach.SetTemplateFunc("datadir", func() string { return geth.Datadir }) attach.SetTemplateFunc("apis", func() string { return apis }) @@ -155,7 +203,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { attach.Expect(` Welcome to the Geth JavaScript console! -instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}} +instance: Geth/v{{gethver}}(quorum-v{{quorumver}})/{{goos}}-{{goarch}}/{{gover}} coinbase: {{etherbase}} at block: 0 ({{niltime}}){{if ipc}} datadir: {{datadir}}{{end}} @@ -172,3 +220,80 @@ func trulyRandInt(lo, hi int) int { num, _ := rand.Int(rand.Reader, big.NewInt(int64(hi-lo))) return int(num.Int64()) + lo } + +// setupIstanbul creates a temporary directory and copies nodekey and genesis.json. +// It initializes istanbul by calling geth init +func setupIstanbul(t *testing.T) string { + datadir := tmpdir(t) + gethPath := filepath.Join(datadir, "geth") + os.Mkdir(gethPath, 0700) + + // Initialize the data directory with the custom genesis block + json := filepath.Join(datadir, "genesis.json") + if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil { + t.Fatalf("failed to write genesis file: %v", err) + } + + nodeKeyFile := filepath.Join(gethPath, "nodekey") + if err := ioutil.WriteFile(nodeKeyFile, []byte(nodeKey), 0600); err != nil { + t.Fatalf("failed to write nodekey file: %v", err) + } + + runGeth(t, "--datadir", datadir, "init", json).WaitExit() + + return datadir +} + +func TestReadTLSClientConfig_whenCustomizeTLSCipherSuites(t *testing.T) { + assert := testifyassert.New(t) + + flagSet := new(flag.FlagSet) + flagSet.Bool(utils.RPCClientTLSInsecureSkipVerify.Name, true, "") + flagSet.String(utils.RPCClientTLSCipherSuites.Name, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "") + ctx := cli.NewContext(nil, flagSet, nil) + + tlsConf, ok, err := readTLSClientConfig("https://arbitraryendpoint", ctx) + + assert.NoError(err) + assert.True(ok, "has custom TLS client configuration") + assert.True(tlsConf.InsecureSkipVerify) + assert.Len(tlsConf.CipherSuites, 2) + assert.Contains(tlsConf.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + assert.Contains(tlsConf.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) +} + +func TestReadTLSClientConfig_whenTypicalTLS(t *testing.T) { + assert := testifyassert.New(t) + + flagSet := new(flag.FlagSet) + ctx := cli.NewContext(nil, flagSet, nil) + + tlsConf, ok, err := readTLSClientConfig("https://arbitraryendpoint", ctx) + + assert.NoError(err) + assert.False(ok, "no custom TLS client configuration") + assert.Nil(tlsConf, "no custom TLS config is set") +} + +func TestReadTLSClientConfig_whenTLSInsecureFlagSet(t *testing.T) { + assert := testifyassert.New(t) + + flagSet := new(flag.FlagSet) + flagSet.Bool(utils.RPCClientTLSInsecureSkipVerify.Name, true, "") + ctx := cli.NewContext(nil, flagSet, nil) + + tlsConf, ok, err := readTLSClientConfig("https://arbitraryendpoint", ctx) + + assert.NoError(err) + assert.True(ok, "has custom TLS client configuration") + assert.True(tlsConf.InsecureSkipVerify) + assert.Len(tlsConf.CipherSuites, 0) +} + +func SetResetPrivateConfig(value string) func() { + existingValue := os.Getenv("PRIVATE_CONFIG") + os.Setenv("PRIVATE_CONFIG", value) + return func() { + os.Setenv("PRIVATE_CONFIG", existingValue) + } +} diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index f63b0dc6c8..1ca6b29fbc 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -105,6 +105,7 @@ func TestDAOForkBlockNewChain(t *testing.T) { } func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBlock *big.Int, expectVote bool) { + defer SetResetPrivateConfig("ignore")() // Create a temporary data directory to use and inspect later datadir := tmpdir(t) defer os.RemoveAll(datadir) diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index ee3991acd1..c58fc436c7 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -20,7 +20,10 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" + + "github.com/cespare/cp" ) var customGenesisTests = []struct { @@ -40,7 +43,7 @@ var customGenesisTests = []struct { "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", - "config" : {} + "config" : {"isQuorum":false } }`, query: "eth.getBlock(0).nonce", result: "0x0000000000001338", @@ -53,29 +56,38 @@ var customGenesisTests = []struct { "difficulty" : "0x20000", "extraData" : "", "gasLimit" : "0x2fefd8", - "nonce" : "0x0000000000001339", + "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", "config" : { "homesteadBlock" : 42, "daoForkBlock" : 141, - "daoForkSupport" : true - } + "daoForkSupport" : true, + "isQuorum" : false + }, }`, query: "eth.getBlock(0).nonce", - result: "0x0000000000001339", + result: "0x0000000000000042", }, } // Tests that initializing Geth with a custom genesis block and chain definitions // work properly. func TestCustomGenesis(t *testing.T) { + defer SetResetPrivateConfig("ignore")() for i, tt := range customGenesisTests { // Create a temporary data directory to use and inspect later datadir := tmpdir(t) defer os.RemoveAll(datadir) + // copy the node key and static-nodes.json so that geth can start with the raft consensus + gethDir := filepath.Join(datadir, "geth") + sourceNodeKey := filepath.Join("testdata", "geth") + if err := cp.CopyAll(gethDir, sourceNodeKey); err != nil { + t.Fatal(err) + } + // Initialize the data directory with the custom genesis block json := filepath.Join(datadir, "genesis.json") if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil { @@ -87,8 +99,91 @@ func TestCustomGenesis(t *testing.T) { geth := runGeth(t, "--nousb", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", + "--raft", "--exec", tt.query, "console") geth.ExpectRegexp(tt.result) geth.ExpectExit() } } + +func TestCustomGenesisUpgradeWithPrivacyEnhancementsBlock(t *testing.T) { + defer SetResetPrivateConfig("ignore")() + // Create a temporary data directory to use and inspect later + datadir := tmpdir(t) + defer os.RemoveAll(datadir) + + // copy the node key and static-nodes.json so that geth can start with the raft consensus + gethDir := filepath.Join(datadir, "geth") + sourceNodeKey := filepath.Join("testdata", "geth") + if err := cp.CopyAll(gethDir, sourceNodeKey); err != nil { + t.Fatal(err) + } + + genesisContentWithoutPrivacyEnhancements := + `{ + "alloc" : {}, + "coinbase" : "0x0000000000000000000000000000000000000000", + "difficulty" : "0x20000", + "extraData" : "", + "gasLimit" : "0x2fefd8", + "nonce" : "0x0000000000000042", + "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp" : "0x00", + "config" : { + "homesteadBlock" : 42, + "daoForkBlock" : 141, + "daoForkSupport" : true, + "isQuorum" : false + } + }` + + // Initialize the data directory with the custom genesis block + json := filepath.Join(datadir, "genesis.json") + if err := ioutil.WriteFile(json, []byte(genesisContentWithoutPrivacyEnhancements), 0600); err != nil { + t.Fatalf("failed to write genesis file: %v", err) + } + geth := runGeth(t, "--datadir", datadir, "init", json) + geth.WaitExit() + + genesisContentWithPrivacyEnhancements := + `{ + "alloc" : {}, + "coinbase" : "0x0000000000000000000000000000000000000000", + "difficulty" : "0x20000", + "extraData" : "", + "gasLimit" : "0x2fefd8", + "nonce" : "0x0000000000000042", + "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp" : "0x00", + "config" : { + "homesteadBlock" : 42, + "daoForkBlock" : 141, + "privacyEnhancementsBlock" : 1000, + "daoForkSupport" : true, + "isQuorum" : false + } + }` + + if err := ioutil.WriteFile(json, []byte(genesisContentWithPrivacyEnhancements), 0600); err != nil { + t.Fatalf("failed to write genesis file: %v", err) + } + geth = runGeth(t, "--datadir", datadir, "init", json) + geth.WaitExit() + + expectedText := "Privacy enhancements have been enabled from block height 1000. Please ensure your privacy manager is upgraded and supports privacy enhancements" + + result := strings.TrimSpace(geth.StderrText()) + if !strings.Contains(result, expectedText) { + geth.Fatalf("bad stderr text. want '%s', got '%s'", expectedText, result) + } + + // start quorum - it should fail the transaction manager PrivacyEnhancements feature validation + geth = runGeth(t, + "--datadir", datadir, "--maxpeers", "0", "--port", "0", + "--nodiscover", "--nat", "none", "--ipcdisable", + "--raft", "console") + geth.ExpectRegexp("Cannot start quorum with privacy enhancements enabled while the transaction manager does not support it") + geth.ExpectExit() +} diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 1bf52d591a..97326d7725 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -132,6 +132,10 @@ func startClient(t *testing.T, name string) *gethrpc { } func TestPriorityClient(t *testing.T) { + // Quorum + t.Skip("skipping test in Quorum (no support for light sync mode).") + // End Quorum + lightServer := startLightServer(t) defer lightServer.killAndWait() diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 1592f774a6..7837f0c985 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -29,18 +29,24 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/pluggable" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/extension/privacyExtension" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/flags" "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" + "github.com/ethereum/go-ethereum/private" gopsutil "github.com/shirou/gopsutil/mem" cli "gopkg.in/urfave/cli.v1" ) @@ -155,6 +161,37 @@ var ( utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, configFileFlag, + // Quorum + utils.QuorumImmutabilityThreshold, + utils.EnableNodePermissionFlag, + utils.RaftModeFlag, + utils.RaftBlockTimeFlag, + utils.RaftJoinExistingFlag, + utils.RaftPortFlag, + utils.RaftDNSEnabledFlag, + utils.EmitCheckpointsFlag, + utils.IstanbulRequestTimeoutFlag, + utils.IstanbulBlockPeriodFlag, + utils.PluginSettingsFlag, + utils.PluginSkipVerifyFlag, + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.AllowedFutureBlockTimeFlag, + utils.EVMCallTimeOutFlag, + utils.MultitenancyFlag, + utils.QuorumPTMUnixSocketFlag, + utils.QuorumPTMUrlFlag, + utils.QuorumPTMTimeoutFlag, + utils.QuorumPTMDialTimeoutFlag, + utils.QuorumPTMHttpIdleTimeoutFlag, + utils.QuorumPTMHttpWriteBufferSizeFlag, + utils.QuorumPTMHttpReadBufferSizeFlag, + utils.QuorumPTMTlsModeFlag, + utils.QuorumPTMTlsRootCaFlag, + utils.QuorumPTMTlsClientCertFlag, + utils.QuorumPTMTlsClientKeyFlag, + utils.QuorumPTMTlsInsecureSkipVerify, + // End-Quorum } rpcFlags = []cli.Flag{ @@ -259,7 +296,15 @@ func init() { app.Flags = append(app.Flags, metricsFlags...) app.Before = func(ctx *cli.Context) error { - return debug.Setup(ctx) + if err := debug.Setup(ctx); err != nil { + return err + } + + if err := quorumInitialisePrivacy(ctx); err != nil { + return err + } + + return nil } app.After = func(ctx *cli.Context) error { debug.Exit() @@ -268,6 +313,22 @@ func init() { } } +// configure and set up quorum transaction privacy +func quorumInitialisePrivacy(ctx *cli.Context) error { + cfg, err := QuorumSetupPrivacyConfiguration(ctx) + if err != nil { + return err + } + + err = private.InitialiseConnection(cfg) + if err != nil { + return err + } + privacyExtension.Init() + + return nil +} + func main() { if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) @@ -349,9 +410,11 @@ func geth(ctx *cli.Context) error { return fmt.Errorf("invalid command: %q", args[0]) } prepare(ctx) + node := makeFullNode(ctx) defer node.Close() startNode(ctx, node) + node.Wait() return nil } @@ -359,12 +422,28 @@ 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) + // raft mode does not support --exitwhensynced + if ctx.GlobalBool(utils.ExitWhenSyncedFlag.Name) && ctx.GlobalBool(utils.RaftModeFlag.Name) { + utils.Fatalf("raft consensus does not support --exitwhensynced") + } + // Start up the node itself utils.StartNode(stack) + // Now that the plugin manager has been started we register the account plugin with the corresponding account backend. All other account management is disabled when using External Signer + if !ctx.IsSet(utils.ExternalSignerFlag.Name) && stack.PluginManager().IsEnabled(plugin.AccountPluginInterfaceName) { + b := stack.AccountManager().Backends(pluggable.BackendType)[0].(*pluggable.Backend) + if err := stack.PluginManager().AddAccountPluginToBackend(b); err != nil { + log.Error("failed to setup account plugin", "err", err) + } + } + // Unlock any account specifically requested unlockAccounts(ctx, stack) @@ -379,6 +458,11 @@ func startNode(ctx *cli.Context, stack *node.Node) { } ethClient := ethclient.NewClient(rpcClient) + var ethService *eth.Ethereum + if err := stack.Service(ðService); 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.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { @@ -396,6 +480,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() { @@ -455,6 +550,22 @@ func startNode(ctx *cli.Context, stack *node.Node) { }() } + // Quorum + // + // checking if permissions is enabled and staring the permissions service + if stack.Config().EnableNodePermission { + stack.Server().SetIsNodePermissioned(permission.IsNodePermissioned) + if stack.IsPermissionEnabled() { + var permissionService *permission.PermissionCtrl + if err := stack.Service(&permissionService); err != nil { + utils.Fatalf("Permission service not runnning: %v", err) + } + if err := permissionService.AfterStart(); err != nil { + utils.Fatalf("Permission service post construct failure: %v", err) + } + } + } + // Start auxiliary services if enabled if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { // Mining only makes sense if a full Ethereum node is running @@ -482,6 +593,9 @@ func startNode(ctx *cli.Context, stack *node.Node) { utils.Fatalf("Failed to start mining: %v", err) } } + + // checks quorum features that depend on the ethereum service + quorumValidateEthService(stack, ctx.GlobalBool(utils.RaftModeFlag.Name)) } // unlockAccounts unlocks any account specifically requested. diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 0e7ee96513..c626a9f95e 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -115,8 +115,10 @@ func version(ctx *cli.Context) error { if gitDate != "" { fmt.Println("Git Commit Date:", gitDate) } + fmt.Println("Quorum Version:", params.QuorumVersion) fmt.Println("Architecture:", runtime.GOARCH) fmt.Println("Protocol Versions:", eth.ProtocolVersions) + fmt.Println("Network Id:", eth.DefaultConfig.NetworkId) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index 29590b63bf..9d8ce515e6 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -196,6 +196,10 @@ type NoRewardEngine struct { rewardsOn bool } +func (sb *NoRewardEngine) Protocol() consensus.Protocol { + return consensus.NorewardsProtocol +} + func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) { return e.inner.Author(header) } @@ -493,7 +497,7 @@ func (api *RetestethAPI) mineBlock() error { } } } - statedb, err := api.blockchain.StateAt(parent.Root()) + statedb, pvtstdb, err := api.blockchain.StateAt(parent.Root()) if err != nil { return err } @@ -516,12 +520,12 @@ func (api *RetestethAPI) mineBlock() error { statedb.Prepare(tx.Hash(), common.Hash{}, txCount) snap := statedb.Snapshot() - receipt, err := core.ApplyTransaction( + receipt, _, err := core.ApplyTransaction( api.chainConfig, api.blockchain, &api.author, gasPool, - statedb, + statedb, pvtstdb, header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(), ) if err != nil { @@ -656,17 +660,17 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, } parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) var root common.Hash - var statedb *state.StateDB + var statedb, pvtst *state.StateDB var err error if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { root = header.Root - statedb, err = api.blockchain.StateAt(root) + statedb, _, err = api.blockchain.StateAt(root) if err != nil { return AccountRangeResult{}, err } } else { root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) + statedb, pvtst, err = api.blockchain.StateAt(root) if err != nil { return AccountRangeResult{}, err } @@ -677,7 +681,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, msg, _ := tx.AsMessage(signer) context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, statedb, pvtst, api.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } @@ -717,7 +721,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) { //fmt.Printf("GetBalance %x, block %d\n", address, blockNr) header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) + statedb, _, err := api.blockchain.StateAt(header.Root) if err != nil { return nil, err } @@ -726,7 +730,7 @@ func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) { header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) + statedb, _, err := api.blockchain.StateAt(header.Root) if err != nil { return nil, err } @@ -735,7 +739,7 @@ func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, bl func (api *RetestethAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) { header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) + statedb, _, err := api.blockchain.StateAt(header.Root) if err != nil { return 0, err } @@ -766,17 +770,17 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context, } parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) var root common.Hash - var statedb *state.StateDB + var statedb, pvtstdb *state.StateDB var err error if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { root = header.Root - statedb, err = api.blockchain.StateAt(root) + statedb, _, err = api.blockchain.StateAt(root) if err != nil { return StorageRangeResult{}, err } } else { root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) + statedb, pvtstdb, err = api.blockchain.StateAt(root) if err != nil { return StorageRangeResult{}, err } @@ -787,7 +791,7 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context, msg, _ := tx.AsMessage(signer) context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, statedb, pvtstdb, api.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } @@ -905,7 +909,7 @@ func retesteth(ctx *cli.Context) error { IdleTimeout: 120 * time.Second, } httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.HTTPListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name)) - httpServer, _, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler) + httpServer, _, _, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler, nil) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } diff --git a/cmd/geth/testdata/clique.json b/cmd/geth/testdata/clique.json index b54b4a7d3b..8d9021df77 100644 --- a/cmd/geth/testdata/clique.json +++ b/cmd/geth/testdata/clique.json @@ -11,7 +11,8 @@ "clique": { "period": 5, "epoch": 30000 - } + }, + "isQuorum": false }, "difficulty": "1", "gasLimit": "8000000", diff --git a/cmd/geth/testdata/geth/nodekey b/cmd/geth/testdata/geth/nodekey new file mode 100644 index 0000000000..c4380139cd --- /dev/null +++ b/cmd/geth/testdata/geth/nodekey @@ -0,0 +1 @@ +1be3b50b31734be48452c29d714941ba165ef0cbf3ccea8ca16c45e3d8d45fb0 \ No newline at end of file diff --git a/cmd/geth/testdata/geth/static-nodes.json b/cmd/geth/testdata/geth/static-nodes.json new file mode 100644 index 0000000000..65d46e9e8e --- /dev/null +++ b/cmd/geth/testdata/geth/static-nodes.json @@ -0,0 +1,3 @@ +[ + "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@127.0.0.1:21000?discport=0&raftport=50401" +] diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index b80f05edb8..bbce032ac7 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -28,6 +28,10 @@ import ( cli "gopkg.in/urfave/cli.v1" ) +// Quorum +var quorumAccountFlagGroup = "QUORUM ACCOUNT" +// End Quorum + // AppHelpFlagGroups is the application flags, grouped by functionality. var AppHelpFlagGroups = []flags.FlagGroup{ { @@ -149,6 +153,11 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, + utils.RPCClientToken, + utils.RPCClientTLSInsecureSkipVerify, + utils.RPCClientTLSCert, + utils.RPCClientTLSCaCert, + utils.RPCClientTLSCipherSuites, }, }, { @@ -197,6 +206,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.VMEnableDebugFlag, utils.EVMInterpreterFlag, utils.EWASMInterpreterFlag, + // Quorum - timout for calls + utils.EVMCallTimeOutFlag, }, }, { @@ -231,6 +242,61 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LegacyGpoPercentileFlag, }, debug.DeprecatedFlags...), }, + // QUORUM + { + Name: "QUORUM", + Flags: []cli.Flag{ + utils.QuorumImmutabilityThreshold, + utils.EnableNodePermissionFlag, + utils.PluginSettingsFlag, + utils.PluginSkipVerifyFlag, + utils.PluginLocalVerifyFlag, + utils.PluginPublicKeyFlag, + utils.AllowedFutureBlockTimeFlag, + utils.MultitenancyFlag, + }, + }, + { + Name: "QUORUM PRIVATE TRANSACTION MANAGER", + Flags: []cli.Flag{ + utils.QuorumPTMUnixSocketFlag, + utils.QuorumPTMUrlFlag, + utils.QuorumPTMTimeoutFlag, + utils.QuorumPTMDialTimeoutFlag, + utils.QuorumPTMHttpIdleTimeoutFlag, + utils.QuorumPTMHttpWriteBufferSizeFlag, + utils.QuorumPTMHttpReadBufferSizeFlag, + utils.QuorumPTMTlsModeFlag, + utils.QuorumPTMTlsRootCaFlag, + utils.QuorumPTMTlsClientCertFlag, + utils.QuorumPTMTlsClientKeyFlag, + utils.QuorumPTMTlsInsecureSkipVerify, + }, + }, + { + Name: quorumAccountFlagGroup, + Flags: []cli.Flag{ + utils.AccountPluginNewAccountConfigFlag, + }, + }, + { + Name: "RAFT", + Flags: []cli.Flag{ + utils.RaftModeFlag, + utils.RaftBlockTimeFlag, + utils.RaftJoinExistingFlag, + utils.RaftPortFlag, + utils.RaftDNSEnabledFlag, + }, + }, + { + Name: "ISTANBUL", + Flags: []cli.Flag{ + utils.IstanbulRequestTimeoutFlag, + utils.IstanbulBlockPeriodFlag, + }, + }, + // END QUORUM { Name: "MISC", Flags: []cli.Flag{ @@ -278,6 +344,14 @@ func init() { AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags = AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags[:miscs] }() } + + // remove the Quorum account options from the main app usage as these should only be used by the geth account sub commands + for i, group := range AppHelpFlagGroups { + if group.Name == quorumAccountFlagGroup { + AppHelpFlagGroups = append(AppHelpFlagGroups[:i], AppHelpFlagGroups[i+1:]...) + } + } + // Render out custom usage screen originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups}) } else if tmpl == flags.CommandHelpTemplate { diff --git a/cmd/puppeth/genesis_test.go b/cmd/puppeth/genesis_test.go index aaa72d73cb..45937d06d3 100644 --- a/cmd/puppeth/genesis_test.go +++ b/cmd/puppeth/genesis_test.go @@ -30,6 +30,10 @@ import ( // Tests the go-ethereum to Aleth chainspec conversion for the Stureby testnet. func TestAlethSturebyConverter(t *testing.T) { + // //Quorum - skip this test as MinGasLimit and GasLimitBoundDivisor has been overridden for quorum + t.Skipf("skipping this test as MinGasLimit and GasLimitBoundDivisor has been overridden for quorum") + + // /Quorum blob, err := ioutil.ReadFile("testdata/stureby_geth.json") if err != nil { t.Fatalf("could not read file: %v", err) @@ -69,6 +73,10 @@ func TestAlethSturebyConverter(t *testing.T) { // Tests the go-ethereum to Parity chainspec conversion for the Stureby testnet. func TestParitySturebyConverter(t *testing.T) { + // //Quorum - skip this test as MinGasLimit and GasLimitBoundDivisor has been overridden for quorum + t.Skipf("skipping this test as MinGasLimit and GasLimitBoundDivisor has been overridden for quorum") + + // /Quorum blob, err := ioutil.ReadFile("testdata/stureby_geth.json") if err != nil { t.Fatalf("could not read file: %v", err) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 30e4f783bf..58cdf53978 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,11 +19,13 @@ package utils import ( "crypto/ecdsa" + "encoding/json" "errors" "fmt" "io" "io/ioutil" "math/big" + "net/url" "os" "path/filepath" "strconv" @@ -36,10 +38,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" + http2 "github.com/ethereum/go-ethereum/common/http" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/istanbul" + istanbulBackend "github.com/ethereum/go-ethereum/consensus/istanbul/backend" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -47,6 +53,7 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethstats" + "github.com/ethereum/go-ethereum/extension" "github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/les" @@ -62,10 +69,15 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/permission" + "github.com/ethereum/go-ethereum/permission/core/types" + "github.com/ethereum/go-ethereum/plugin" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/raft" "github.com/ethereum/go-ethereum/rpc" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" pcsclite "github.com/gballet/go-libpcsclite" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) func init() { @@ -468,6 +480,27 @@ var ( Name: "nocompaction", Usage: "Disables db compaction after import", } + // RPC Client Settings + RPCClientToken = cli.StringFlag{ + Name: "rpcclitoken", + Usage: "RPC Client access token", + } + RPCClientTLSCert = cli.StringFlag{ + Name: "rpcclitls.cert", + Usage: "Server's TLS certificate PEM file on connection by client", + } + RPCClientTLSCaCert = cli.StringFlag{ + Name: "rpcclitls.cacert", + Usage: "CA certificate PEM file for provided server's TLS certificate on connection by client", + } + RPCClientTLSCipherSuites = cli.StringFlag{ + Name: "rpcclitls.ciphersuites", + Usage: "Customize supported cipher suites when using TLS connection. Value is a comma-separated cipher suite string", + } + RPCClientTLSInsecureSkipVerify = cli.BoolFlag{ + Name: "rpcclitls.insecureskipverify", + Usage: "Disable verification of server's TLS certificate on connection by client", + } // RPC settings IPCDisabledFlag = cli.BoolFlag{ Name: "ipcdisable", @@ -718,6 +751,154 @@ var ( Usage: "External EVM configuration (default = built-in interpreter)", Value: "", } + + // Quorum - added configurable call timeout for execution of calls + EVMCallTimeOutFlag = cli.IntFlag{ + Name: "vm.calltimeout", + Usage: "Timeout duration in seconds for message call execution without creating a transaction. Value 0 means no timeout.", + Value: 5, + } + + // Quorum + // immutability threshold which can be passed as a parameter at geth start + QuorumImmutabilityThreshold = cli.IntFlag{ + Name: "immutabilitythreshold", + Usage: "overrides the default immutability threshold for Quorum nodes. Its the threshold beyond which block data will be moved to ancient db", + Value: 3162240, + } + // Raft flags + RaftModeFlag = cli.BoolFlag{ + Name: "raft", + Usage: "If enabled, uses Raft instead of Quorum Chain for consensus", + } + RaftBlockTimeFlag = cli.IntFlag{ + Name: "raftblocktime", + Usage: "Amount of time between raft block creations in milliseconds", + Value: 50, + } + RaftJoinExistingFlag = cli.IntFlag{ + Name: "raftjoinexisting", + Usage: "The raft ID to assume when joining an pre-existing cluster", + Value: 0, + } + + EmitCheckpointsFlag = cli.BoolFlag{ + Name: "emitcheckpoints", + Usage: "If enabled, emit specially formatted logging checkpoints", + } + RaftPortFlag = cli.IntFlag{ + Name: "raftport", + Usage: "The port to bind for the raft transport", + Value: 50400, + } + RaftDNSEnabledFlag = cli.BoolFlag{ + Name: "raftdnsenable", + Usage: "Enable DNS resolution of peers", + } + + // Permission + EnableNodePermissionFlag = cli.BoolFlag{ + Name: "permissioned", + Usage: "If enabled, the node will allow only a defined list of nodes to connect", + } + AllowedFutureBlockTimeFlag = cli.Uint64Flag{ + Name: "allowedfutureblocktime", + Usage: "Max time (in seconds) from current time allowed for blocks, before they're considered future blocks", + Value: 0, + } + // Plugins settings + PluginSettingsFlag = cli.StringFlag{ + Name: "plugins", + Usage: "The URI of configuration which describes plugins being used. E.g.: file:///opt/geth/plugins.json", + } + PluginLocalVerifyFlag = cli.BoolFlag{ + Name: "plugins.localverify", + Usage: "If enabled, verify plugin integrity from local file system. This requires plugin signature file and PGP public key file to be available", + } + PluginPublicKeyFlag = cli.StringFlag{ + Name: "plugins.publickey", + Usage: fmt.Sprintf("The URI of PGP public key for local plugin verification. E.g.: file:///opt/geth/pubkey.pgp.asc. This flag is only valid if --%s is set (default = file:////%s)", PluginLocalVerifyFlag.Name, plugin.DefaultPublicKeyFile), + } + PluginSkipVerifyFlag = cli.BoolFlag{ + Name: "plugins.skipverify", + Usage: "If enabled, plugin integrity is NOT verified", + } + // account plugin flags + AccountPluginNewAccountConfigFlag = cli.StringFlag{ + Name: "plugins.account.config", + Usage: "Value will be passed to an account plugin if being used. See the account plugin implementation's documentation for further details", + } + // Istanbul settings + IstanbulRequestTimeoutFlag = cli.Uint64Flag{ + Name: "istanbul.requesttimeout", + Usage: "Timeout for each Istanbul round in milliseconds", + Value: eth.DefaultConfig.Istanbul.RequestTimeout, + } + IstanbulBlockPeriodFlag = cli.Uint64Flag{ + Name: "istanbul.blockperiod", + 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.", + } + + // Quorum Private Transaction Manager connection options + QuorumPTMUnixSocketFlag = DirectoryFlag{ + Name: "ptm.socket", + Usage: "Path to the ipc file when using unix domain socket for the private transaction manager connection", + } + QuorumPTMUrlFlag = cli.StringFlag{ + Name: "ptm.url", + Usage: "URL when using http connection to private transaction manager", + } + QuorumPTMTimeoutFlag = cli.UintFlag{ + Name: "ptm.timeout", + Usage: "Timeout (seconds) for the private transaction manager connection. Zero value means timeout disabled.", + Value: http2.DefaultConfig.Timeout, + } + QuorumPTMDialTimeoutFlag = cli.UintFlag{ + Name: "ptm.dialtimeout", + Usage: "Dial timeout (seconds) for the private transaction manager connection. Zero value means timeout disabled.", + Value: http2.DefaultConfig.DialTimeout, + } + QuorumPTMHttpIdleTimeoutFlag = cli.UintFlag{ + Name: "ptm.http.idletimeout", + Usage: "Idle timeout (seconds) for the private transaction manager connection. Zero value means timeout disabled.", + Value: http2.DefaultConfig.HttpIdleConnTimeout, + } + QuorumPTMHttpWriteBufferSizeFlag = cli.IntFlag{ + Name: "ptm.http.writebuffersize", + Usage: "Size of the write buffer (bytes) for the private transaction manager connection. Zero value uses http.Transport default.", + Value: 0, + } + QuorumPTMHttpReadBufferSizeFlag = cli.IntFlag{ + Name: "ptm.http.readbuffersize", + Usage: "Size of the read buffer (bytes) for the private transaction manager connection. Zero value uses http.Transport default.", + Value: 0, + } + QuorumPTMTlsModeFlag = cli.StringFlag{ + Name: "ptm.tls.mode", + Usage: `If "off" then TLS disabled (default). If "strict" then will use TLS for http connection to private transaction manager`, + } + QuorumPTMTlsRootCaFlag = DirectoryFlag{ + Name: "ptm.tls.rootca", + Usage: "Path to file containing root CA certificate for TLS connection to private transaction manager (defaults to host's certificates)", + } + QuorumPTMTlsClientCertFlag = DirectoryFlag{ + Name: "ptm.tls.clientcert", + Usage: "Path to file containing client certificate (or chain of certs) for TLS connection to private transaction manager", + } + QuorumPTMTlsClientKeyFlag = DirectoryFlag{ + Name: "ptm.tls.clientkey", + Usage: "Path to file containing client's private key for TLS connection to private transaction manager", + } + QuorumPTMTlsInsecureSkipVerify = cli.BoolFlag{ + Name: "ptm.tls.insecureskipverify", + Usage: "Disable verification of server's TLS certificate on connection to private transaction manager", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1231,6 +1412,12 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(InsecureUnlockAllowedFlag.Name) { cfg.InsecureUnlockAllowed = ctx.GlobalBool(InsecureUnlockAllowedFlag.Name) } + + // Quorum + if ctx.GlobalIsSet(EnableNodePermissionFlag.Name) { + cfg.EnableNodePermission = ctx.GlobalBool(EnableNodePermissionFlag.Name) + } + } func setSmartCard(ctx *cli.Context, cfg *node.Config) { @@ -1276,6 +1463,51 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { case ctx.GlobalBool(YoloV1Flag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v1") } + if err := SetPlugins(ctx, cfg); err != nil { + Fatalf(err.Error()) + } +} + +// Quorum +// +// Read plugin settings from --plugins flag. Overwrite settings defined in --config if any +func SetPlugins(ctx *cli.Context, cfg *node.Config) error { + if ctx.GlobalIsSet(PluginSettingsFlag.Name) { + // validate flag combination + if ctx.GlobalBool(PluginSkipVerifyFlag.Name) && ctx.GlobalBool(PluginLocalVerifyFlag.Name) { + return fmt.Errorf("only --%s or --%s must be set", PluginSkipVerifyFlag.Name, PluginLocalVerifyFlag.Name) + } + if !ctx.GlobalBool(PluginLocalVerifyFlag.Name) && ctx.GlobalIsSet(PluginPublicKeyFlag.Name) { + return fmt.Errorf("--%s is required for setting --%s", PluginLocalVerifyFlag.Name, PluginPublicKeyFlag.Name) + } + pluginSettingsURL, err := url.Parse(ctx.GlobalString(PluginSettingsFlag.Name)) + if err != nil { + return fmt.Errorf("plugins: Invalid URL for --%s due to %s", PluginSettingsFlag.Name, err) + } + var pluginSettings plugin.Settings + r, err := urlReader(pluginSettingsURL) + if err != nil { + return fmt.Errorf("plugins: unable to create reader due to %s", err) + } + defer func() { + _ = r.Close() + }() + if err := json.NewDecoder(r).Decode(&pluginSettings); err != nil { + return fmt.Errorf("plugins: unable to parse settings due to %s", err) + } + pluginSettings.SetDefaults() + cfg.Plugins = &pluginSettings + } + return nil +} + +func urlReader(u *url.URL) (io.ReadCloser, error) { + s := u.Scheme + switch s { + case "file": + return os.Open(filepath.Join(u.Host, u.Path)) + } + return nil, fmt.Errorf("unsupported scheme %s", s) } func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { @@ -1405,6 +1637,9 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { if ctx.GlobalIsSet(MinerNoVerfiyFlag.Name) { cfg.Noverify = ctx.GlobalBool(MinerNoVerfiyFlag.Name) } + if ctx.GlobalIsSet(AllowedFutureBlockTimeFlag.Name) { + cfg.AllowedFutureBlockTime = ctx.GlobalUint64(AllowedFutureBlockTimeFlag.Name) //Quorum + } } func setWhitelist(ctx *cli.Context, cfg *eth.Config) { @@ -1430,6 +1665,27 @@ func setWhitelist(ctx *cli.Context, cfg *eth.Config) { } } +// Quorum +func setIstanbul(ctx *cli.Context, cfg *eth.Config) { + if ctx.GlobalIsSet(IstanbulRequestTimeoutFlag.Name) { + cfg.Istanbul.RequestTimeout = ctx.GlobalUint64(IstanbulRequestTimeoutFlag.Name) + } + if ctx.GlobalIsSet(IstanbulBlockPeriodFlag.Name) { + cfg.Istanbul.BlockPeriod = ctx.GlobalUint64(IstanbulBlockPeriodFlag.Name) + } +} + +func setRaft(ctx *cli.Context, cfg *eth.Config) { + cfg.RaftMode = ctx.GlobalBool(RaftModeFlag.Name) +} + +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) +} + // CheckExclusive verifies that only a single instance of the provided flags was // set by the user. Each flag might optionally be followed by a string type to // specialize it further. @@ -1507,6 +1763,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { setWhitelist(ctx, cfg) setLes(ctx, cfg) + // Quorum + setQuorumConfig(ctx, cfg) + if ctx.GlobalIsSet(SyncModeFlag.Name) { cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode) } @@ -1580,6 +1839,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } } + // set immutability threshold in config + params.SetQuorumImmutabilityThreshold(ctx.GlobalInt(QuorumImmutabilityThreshold.Name)) + // Override any default configs for hard coded networks. switch { case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): @@ -1655,7 +1917,9 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *eth.Config) { +func RegisterEthService(stack *node.Node, cfg *eth.Config) chan *eth.Ethereum { + // Quorum: raft service listens to this channel to get Ethereum backend + nodeChan := make(chan *eth.Ethereum, 1) var err error if cfg.SyncMode == downloader.LightSync { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { @@ -1668,12 +1932,15 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) } + nodeChan <- fullNode return fullNode, err }) } if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } + + return nodeChan } // RegisterShhService configures Whisper and adds it to the given node. @@ -1723,6 +1990,102 @@ func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []st } } +// Quorum +// +// Register plugin manager as a service in geth +func RegisterPluginService(stack *node.Node, cfg *node.Config, skipVerify bool, localVerify bool, publicKey string) { + if err := cfg.ResolvePluginBaseDir(); err != nil { + Fatalf("plugins: unable to resolve plugin base dir due to %s", err) + } + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + return plugin.NewPluginManager(cfg.UserIdent, cfg.Plugins, skipVerify, localVerify, publicKey) + }); err != nil { + Fatalf("plugins: Failed to register the Plugins service: %v", err) + } +} + +// Configure smart-contract-based permissioning service +func RegisterPermissionService(stack *node.Node, useDns bool) { + if err := stack.Register(func(sctx *node.ServiceContext) (node.Service, error) { + permissionConfig, err := types.ParsePermissionConfig(stack.DataDir()) + if err != nil { + return nil, fmt.Errorf("loading of %s failed due to %v", params.PERMISSION_MODEL_CONFIG, err) + } + // start the permissions management service + pc, err := permission.NewQuorumPermissionCtrl(stack, &permissionConfig, useDns) + if err != nil { + return nil, fmt.Errorf("failed to load the permission contracts as given in %s due to %v", params.PERMISSION_MODEL_CONFIG, err) + } + return pc, nil + }); err != nil { + Fatalf("Failed to register the permission service: %v", err) + } + log.Info("permission service registered") +} + +func RegisterRaftService(stack *node.Node, ctx *cli.Context, nodeCfg *node.Config, ethChan chan *eth.Ethereum) { + blockTimeMillis := ctx.GlobalInt(RaftBlockTimeFlag.Name) + datadir := ctx.GlobalString(DataDirFlag.Name) + joinExistingId := ctx.GlobalInt(RaftJoinExistingFlag.Name) + useDns := ctx.GlobalBool(RaftDNSEnabledFlag.Name) + raftPort := uint16(ctx.GlobalInt(RaftPortFlag.Name)) + + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + privkey := nodeCfg.NodeKey() + strId := enode.PubkeyToIDV4(&privkey.PublicKey).String() + blockTimeNanos := time.Duration(blockTimeMillis) * time.Millisecond + peers := nodeCfg.StaticNodes() + + var myId uint16 + var joinExisting bool + + if joinExistingId > 0 { + myId = uint16(joinExistingId) + joinExisting = true + } else if len(peers) == 0 { + Fatalf("Raft-based consensus requires either (1) an initial peers list (in static-nodes.json) including this enode hash (%v), or (2) the flag --raftjoinexisting RAFT_ID, where RAFT_ID has been issued by an existing cluster member calling `raft.addPeer(ENODE_ID)` with an enode ID containing this node's enode hash.", strId) + } else { + peerIds := make([]string, len(peers)) + + for peerIdx, peer := range peers { + if !peer.HasRaftPort() { + Fatalf("raftport querystring parameter not specified in static-node enode ID: %v. please check your static-nodes.json file.", peer.String()) + } + + peerId := peer.ID().String() + peerIds[peerIdx] = peerId + if peerId == strId { + myId = uint16(peerIdx) + 1 + } + } + + if myId == 0 { + Fatalf("failed to find local enode ID (%v) amongst peer IDs: %v", strId, peerIds) + } + } + + ethereum := <-ethChan + ethChan <- ethereum + return raft.New(ctx, ethereum.BlockChain().Config(), myId, raftPort, joinExisting, blockTimeNanos, ethereum, peers, datadir, useDns) + }); err != nil { + Fatalf("Failed to register the Raft service: %v", err) + } + log.Info("raft service registered") +} + +func RegisterExtensionService(stack *node.Node, ethChan chan *eth.Ethereum) { + registerFunc := func(ctx *node.ServiceContext) (node.Service, error) { + factory, err := extension.NewServicesFactory(stack, private.P, <-ethChan) + if err != nil { + return nil, err + } + return factory.BackendService(), nil + } + if err := stack.Register(registerFunc); err != nil { + Fatalf("Failed to register the Extension service: %v", err) + } +} + func SetupMetrics(ctx *cli.Context) { if metrics.Enabled { log.Info("Enabling metrics collection") @@ -1808,16 +2171,41 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { } // MakeChain creates a chain manager from set command line flags. -func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.BlockChain, chainDb ethdb.Database) { - var err error +func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool, useExist bool) (chain *core.BlockChain, chainDb ethdb.Database) { + var ( + config *params.ChainConfig + err error + ) chainDb = MakeChainDatabase(ctx, stack) - config, _, err := core.SetupGenesisBlock(chainDb, MakeGenesis(ctx)) - if err != nil { - Fatalf("%v", err) + + if useExist { + stored := rawdb.ReadCanonicalHash(chainDb, 0) + if (stored == common.Hash{}) { + Fatalf("No existing genesis") + } + config = rawdb.ReadChainConfig(chainDb, stored) + } else { + config, _, err = core.SetupGenesisBlock(chainDb, MakeGenesis(ctx)) + if err != nil { + Fatalf("%v", err) + } } + var engine consensus.Engine if config.Clique != nil { engine = clique.New(config.Clique, chainDb) + } else if config.Istanbul != nil { + // for IBFT + istanbulConfig := istanbul.DefaultConfig + if config.Istanbul.Epoch != 0 { + istanbulConfig.Epoch = config.Istanbul.Epoch + } + istanbulConfig.ProposerPolicy = istanbul.ProposerPolicy(config.Istanbul.ProposerPolicy) + istanbulConfig.Ceil2Nby3Block = config.Istanbul.Ceil2Nby3Block + engine = istanbulBackend.New(istanbulConfig, stack.GetNodeKey(), chainDb) + } else if config.IsQuorum { + // for Raft + engine = ethash.NewFullFaker() } else { engine = ethash.NewFaker() if !ctx.GlobalBool(FakePoWFlag.Name) { diff --git a/cmd/utils/flags_test.go b/cmd/utils/flags_test.go index adfdd0903e..15ff4313ff 100644 --- a/cmd/utils/flags_test.go +++ b/cmd/utils/flags_test.go @@ -18,10 +18,103 @@ package utils import ( + "flag" + "io/ioutil" + "os" + "path" "reflect" + "strconv" "testing" + + "github.com/ethereum/go-ethereum/node" + "github.com/stretchr/testify/assert" + "gopkg.in/urfave/cli.v1" ) +func TestSetPlugins_whenPluginsNotEnabled(t *testing.T) { + arbitraryNodeConfig := &node.Config{} + arbitraryCLIContext := cli.NewContext(nil, &flag.FlagSet{}, nil) + + assert.NoError(t, SetPlugins(arbitraryCLIContext, arbitraryNodeConfig)) + + assert.Nil(t, arbitraryNodeConfig.Plugins) +} + +func TestSetPlugins_whenInvalidFlagsCombination(t *testing.T) { + arbitraryNodeConfig := &node.Config{} + fs := &flag.FlagSet{} + fs.String(PluginSettingsFlag.Name, "", "") + fs.Bool(PluginSkipVerifyFlag.Name, true, "") + fs.Bool(PluginLocalVerifyFlag.Name, true, "") + fs.String(PluginPublicKeyFlag.Name, "", "") + arbitraryCLIContext := cli.NewContext(nil, fs, nil) + assert.NoError(t, arbitraryCLIContext.GlobalSet(PluginSettingsFlag.Name, "arbitrary value")) + + verifyErrorMessage(t, arbitraryCLIContext, arbitraryNodeConfig, "only --plugins.skipverify or --plugins.localverify must be set") + + assert.NoError(t, arbitraryCLIContext.GlobalSet(PluginSkipVerifyFlag.Name, "false")) + assert.NoError(t, arbitraryCLIContext.GlobalSet(PluginLocalVerifyFlag.Name, "false")) + assert.NoError(t, arbitraryCLIContext.GlobalSet(PluginPublicKeyFlag.Name, "arbitry value")) + + verifyErrorMessage(t, arbitraryCLIContext, arbitraryNodeConfig, "--plugins.localverify is required for setting --plugins.publickey") +} + +func TestSetPlugins_whenInvalidPluginSettingsURL(t *testing.T) { + arbitraryNodeConfig := &node.Config{} + fs := &flag.FlagSet{} + fs.String(PluginSettingsFlag.Name, "", "") + arbitraryCLIContext := cli.NewContext(nil, fs, nil) + assert.NoError(t, arbitraryCLIContext.GlobalSet(PluginSettingsFlag.Name, "arbitrary value")) + + verifyErrorMessage(t, arbitraryCLIContext, arbitraryNodeConfig, "plugins: unable to create reader due to unsupported scheme ") +} + +func TestSetImmutabilityThreshold(t *testing.T) { + fs := &flag.FlagSet{} + fs.Int(QuorumImmutabilityThreshold.Name, 0, "") + arbitraryCLIContext := cli.NewContext(nil, fs, nil) + assert.NoError(t, arbitraryCLIContext.GlobalSet(QuorumImmutabilityThreshold.Name, strconv.Itoa(100000))) + assert.True(t, arbitraryCLIContext.GlobalIsSet(QuorumImmutabilityThreshold.Name), "immutability threshold flag not set") + assert.Equal(t, 100000, arbitraryCLIContext.GlobalInt(QuorumImmutabilityThreshold.Name), "immutability threshold value not set") +} + +func TestSetTimeOutForCall(t *testing.T) { + fs := &flag.FlagSet{} + fs.Int(EVMCallTimeOutFlag.Name, 0, "") + arbitraryCLIContext := cli.NewContext(nil, fs, nil) + assert.NoError(t, arbitraryCLIContext.GlobalSet(EVMCallTimeOutFlag.Name, strconv.Itoa(10))) + assert.True(t, arbitraryCLIContext.GlobalIsSet(EVMCallTimeOutFlag.Name), "timeoutforcall flag not set") + assert.Equal(t, 10, arbitraryCLIContext.GlobalInt(EVMCallTimeOutFlag.Name), "timeoutforcall value not set") +} + +func TestSetPlugins_whenTypical(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + arbitraryJSONFile := path.Join(tmpDir, "arbitary.json") + if err := ioutil.WriteFile(arbitraryJSONFile, []byte("{}"), 0644); err != nil { + t.Fatal(err) + } + arbitraryNodeConfig := &node.Config{} + fs := &flag.FlagSet{} + fs.String(PluginSettingsFlag.Name, "", "") + arbitraryCLIContext := cli.NewContext(nil, fs, nil) + assert.NoError(t, arbitraryCLIContext.GlobalSet(PluginSettingsFlag.Name, "file://"+arbitraryJSONFile)) + + assert.NoError(t, SetPlugins(arbitraryCLIContext, arbitraryNodeConfig)) + + assert.NotNil(t, arbitraryNodeConfig.Plugins) +} + +func verifyErrorMessage(t *testing.T, ctx *cli.Context, cfg *node.Config, expectedMsg string) { + err := SetPlugins(ctx, cfg) + assert.EqualError(t, err, expectedMsg) +} + func Test_SplitTagsFlag(t *testing.T) { tests := []struct { name string diff --git a/common/http/certificate.go b/common/http/certificate.go new file mode 100644 index 0000000000..7a9bd9c36f --- /dev/null +++ b/common/http/certificate.go @@ -0,0 +1,61 @@ +package http + +import ( + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/ethereum/go-ethereum/log" +) + +// Load Root CA certificate(s). +// Path can be a single certificate file, or a comma separated list containing a combination of +// certificate files or directories containing certificate files. +func loadRootCaCerts(rootCAPath string) (*x509.CertPool, error) { + rootCAPool, err := x509.SystemCertPool() + if err != nil { + rootCAPool = x509.NewCertPool() + } + if len(rootCAPath) == 0 { + return rootCAPool, nil + } + + list := strings.Split(rootCAPath, ",") + for _, thisFileOrDirEntry := range list { + info, err := os.Lstat(thisFileOrDirEntry) + if err != nil { + return nil, fmt.Errorf("unable to check whether RootCA entry '%v' is a file or directory, due to: %s", thisFileOrDirEntry, err) + } + + if info.Mode()&os.ModeDir != 0 { + fileList, err := ioutil.ReadDir(thisFileOrDirEntry) + if err != nil { + return nil, fmt.Errorf("unable to read contents of RootCA directory '%v', due to: %s", thisFileOrDirEntry, err) + } + + for _, fileinfo := range fileList { + if err := loadRootCAFromFile(thisFileOrDirEntry+"/"+fileinfo.Name(), rootCAPool); err != nil { + return nil, err + } + } + } else if err := loadRootCAFromFile(thisFileOrDirEntry, rootCAPool); err != nil { + return nil, err + } + } + + return rootCAPool, nil +} + +func loadRootCAFromFile(file string, roots *x509.CertPool) error { + log.Debug("loading RootCA certificate for connection to private transaction manager", "file", file) + data, err := ioutil.ReadFile(file) + if err != nil { + return fmt.Errorf("unable to read contents of RootCA certificate file '%v', due to: %s", file, err) + } + if !roots.AppendCertsFromPEM(data) { + return fmt.Errorf("failed to add TlsRootCA certificate to pool, check that '%v' contains a valid RootCA certificate", file) + } + return nil +} diff --git a/common/http/client.go b/common/http/client.go new file mode 100644 index 0000000000..3de525ef9f --- /dev/null +++ b/common/http/client.go @@ -0,0 +1,49 @@ +package http + +import ( + "fmt" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private/engine" +) + +func CreateClient(cfg Config) (*engine.Client, error) { + var client *engine.Client + if IsSocketConfigured(cfg) { + + log.Info("Connecting to private tx manager using IPC socket") + client = &engine.Client{ + HttpClient: &http.Client{ + Transport: unixTransport(cfg), + }, + BaseURL: "http+unix://c", + } + + } else { + + transport := httpTransport(cfg) + if cfg.TlsMode == TlsOff { + log.Info("Connecting to private tx manager using HTTP") + } else { + log.Info("Connecting to private tx manager using HTTPS") + tlsConfig, err := newTLSConfig(cfg) + if err != nil { + return nil, fmt.Errorf("unable to create http.client to private tx manager due to: %s", err) + } + transport.TLSClientConfig = tlsConfig + } + + client = &engine.Client{ + HttpClient: &http.Client{ + Timeout: time.Duration(cfg.Timeout) * time.Second, + Transport: transport, + }, + BaseURL: cfg.HttpUrl, + } + + } + + return client, nil +} diff --git a/common/http/config.go b/common/http/config.go new file mode 100644 index 0000000000..1eb22a783a --- /dev/null +++ b/common/http/config.go @@ -0,0 +1,206 @@ +package http + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" +) + +const ( + NoConnection string = "none" + UnixDomainSocketConnection string = "unix" + HttpConnection string = "http" +) + +const ( + TlsOff string = "off" + TlsStrict string = "strict" +) + +type Config struct { + ConnectionType string `toml:"-"` // connection type is not loaded from toml + Socket string // filename for unix domain socket + WorkDir string // directory for unix domain socket + HttpUrl string // transaction manager URL for HTTP connection + Timeout uint // timeout for overall client call (seconds), zero means timeout disabled + DialTimeout uint // timeout for connecting to unix socket (seconds) + HttpIdleConnTimeout uint // timeout for idle http connection (seconds), zero means timeout disabled + HttpWriteBufferSize int // size of http connection write buffer (bytes), if zero then uses http.Transport default + HttpReadBufferSize int // size of http connection read buffer (bytes), if zero then uses http.Transport default + TlsMode string // whether TLS is enabled on HTTP connection (can be "off" or "strict") + TlsRootCA string // path to file containing certificate for root CA (defaults to host's certificates) + TlsClientCert string // path to file containing client certificate (or chain of certs) + TlsClientKey string // path to file containing client's private key + TlsInsecureSkipVerify bool // if true then does not verify that server certificate is CA signed +} + +var NoConnectionConfig = Config{ + ConnectionType: NoConnection, + TlsMode: TlsOff, +} + +var DefaultConfig = Config{ + Timeout: 5, + DialTimeout: 1, + HttpIdleConnTimeout: 10, + TlsMode: TlsOff, +} + +func IsSocketConfigured(cfg Config) bool { + return cfg.ConnectionType == UnixDomainSocketConnection +} + +// This will accept path as any of the following and return relevant configuration: +// - path set to "ignore" +// - path to an ipc file +// - path to a config file +func FetchConfigOrIgnore(path string) (Config, error) { + if path == "" || strings.EqualFold(path, "ignore") { + return NoConnectionConfig, nil + } + + return FetchConfig(path) +} + +// FetchConfig sets up the configuration for the connection to a txn manager. +// It will accept a path to an ipc file or a path to a config file, +// and returns the full configuration info for the specified type of connection. +func FetchConfig(path string) (Config, error) { + info, err := os.Lstat(path) + if err != nil { + return Config{}, fmt.Errorf("unable to check whether connection details are specified as a config file or ipc file '%s', due to: %s", path, err) + } + + var cfg Config + isSocket := info.Mode()&os.ModeSocket != 0 + if isSocket { + cfg = DefaultConfig + cfg.ConnectionType = UnixDomainSocketConnection + cfg.WorkDir, cfg.Socket = filepath.Split(path) + } else { + cfg, err = LoadConfigFile(path) + if err != nil { + return Config{}, fmt.Errorf("error reading config from '%s' due to: %s", path, err) + } + } + + return cfg, nil +} + +func LoadConfigFile(path string) (Config, error) { + cfg := DefaultConfig + if _, err := toml.DecodeFile(path, &cfg); err != nil { + return Config{}, err + } + cfg.TlsMode = strings.ToLower(cfg.TlsMode) + + if cfg.Socket != "" { + cfg.ConnectionType = UnixDomainSocketConnection + } else if cfg.HttpUrl != "" { + cfg.ConnectionType = HttpConnection + } else { + return Config{}, fmt.Errorf("either Socket or HTTP connection must be specified in config file") + } + + return cfg, nil +} + +func (cfg *Config) Validate() error { + switch cfg.ConnectionType { + case "": // no connection type defined + case NoConnection: + case UnixDomainSocketConnection: + if len(cfg.Socket) == 0 { //sanity check - should never occur + return fmt.Errorf("ipc file configuration is missing for private transaction manager connection") + } + if len(cfg.HttpUrl) != 0 { + return fmt.Errorf("HTTP URL and unix ipc file cannot both be specified for private transaction manager connection") + } + if cfg.TlsMode != TlsOff { + return fmt.Errorf("TLS is not supported over unix domain socket for private transaction manager connection") + } + case HttpConnection: + if len(cfg.Socket) != 0 { + return fmt.Errorf("HTTP URL and unix ipc file cannot both be specified for private transaction manager connection") + } + if len(cfg.HttpUrl) == 0 { //sanity check - should never occur + return fmt.Errorf("URL configuration is missing for private transaction manager HTTP connection") + } + switch cfg.TlsMode { + case TlsOff: + //no action needed + case TlsStrict: + if !strings.Contains(strings.ToLower(cfg.HttpUrl), "https") { + return fmt.Errorf("connection is configured with TLS but HTTPS url is not specified") + } + if (len(cfg.TlsClientCert) == 0 && len(cfg.TlsClientKey) != 0) || (len(cfg.TlsClientCert) != 0 && len(cfg.TlsClientKey) == 0) { + return fmt.Errorf("invalid details for HTTP connection with TLS, configuration must specify both clientCert and clientKey, or neither one") + } + default: + return fmt.Errorf("invalid value for TLS mode in config file, must be either OFF or STRICT") + } + } + + return nil +} + +// +// Setters for the various config fields +// + +func (cfg *Config) SetSocket(socketPath string) { + cfg.ConnectionType = UnixDomainSocketConnection + workDir, socketFilename := filepath.Split(socketPath) + if workDir != "" { + cfg.WorkDir = workDir + } + cfg.Socket = socketFilename +} + +func (cfg *Config) SetHttpUrl(httpUrl string) { + cfg.ConnectionType = HttpConnection + cfg.HttpUrl = httpUrl +} + +func (cfg *Config) SetTimeout(timeout uint) { + cfg.Timeout = timeout +} + +func (cfg *Config) SetDialTimeout(dialTimeout uint) { + cfg.DialTimeout = dialTimeout +} + +func (cfg *Config) SetHttpIdleConnTimeout(httpIdleConnTimeout uint) { + cfg.HttpIdleConnTimeout = httpIdleConnTimeout +} + +func (cfg *Config) SetHttpWriteBufferSize(httpWriteBufferSize int) { + cfg.HttpWriteBufferSize = httpWriteBufferSize +} + +func (cfg *Config) SetHttpReadBufferSize(httpReadBufferSize int) { + cfg.HttpReadBufferSize = httpReadBufferSize +} + +func (cfg *Config) SetTlsMode(tlsMode string) { + cfg.TlsMode = tlsMode +} + +func (cfg *Config) SetTlsRootCA(tlsRootCA string) { + cfg.TlsRootCA = tlsRootCA +} + +func (cfg *Config) SetTlsClientCert(tlsClientCert string) { + cfg.TlsClientCert = tlsClientCert +} + +func (cfg *Config) SetTlsClientKey(tlsClientKey string) { + cfg.TlsClientKey = tlsClientKey +} + +func (cfg *Config) SetTlsInsecureSkipVerify(tlsInsecureSkipVerify bool) { + cfg.TlsInsecureSkipVerify = tlsInsecureSkipVerify +} diff --git a/common/http/config_test.go b/common/http/config_test.go new file mode 100644 index 0000000000..86fbf1e777 --- /dev/null +++ b/common/http/config_test.go @@ -0,0 +1,342 @@ +package http + +import ( + "io/ioutil" + "net" + "os" + "path/filepath" + "runtime" + "syscall" + "testing" + + "github.com/stretchr/testify/assert" +) + +var socketConfigFileWithTimeouts = ` +socket = "tm.ipc" +workdir = "qdata/c1" +dialTimeout = 8 +timeout = 9 +` +var socketConfigFileNoTimeouts = ` +socket = "tm.ipc" +workdir = "qdata/c1" +` +var httpConfigFileWithTimeouts = ` +httpUrl = "http:localhost:9101" +tlsMode = "OFF" +timeout = 101 +httpIdleConnTimeout = 102 +httpWriteBufferSize = 1001 +httpReadBufferSize = 1002 +` +var httpConfigFileWithInvalidTlsMode = ` +httpUrl = "http:localhost:9101" +tlsMode = "ABC" +` +var httpTlsConfigFileWithTimeouts = ` +httpUrl = "https:localhost:9101" +tlsMode = "STRICT" +tlsRootCA = "mydir/rootca.cert.pem" +tlsClientCert = "mydir/client.cert.pem" +tlsClientKey = "mydir/client.key.pem" +timeout = 101 +httpIdleConnTimeout = 102 +httpWriteBufferSize = 1001 +httpReadBufferSize = 1002 +` +var httpTlsConfigFileNoTimeouts = ` +httpUrl = "https:localhost:9101" +tlsMode = "strict" +tlsRootCA = "mydir/rootca.cert.pem" +tlsClientCert = "mydir/client.cert.pem" +tlsClientKey = "mydir/client.key.pem" +tlsInsecureSkipVerify = true +` +var httpTlsConfigFileNoRootCert = ` +httpUrl = "https:localhost:9101" +tlsMode = "STRICT" +tlsClientCert = "mydir/client.cert.pem" +tlsClientKey = "mydir/client.key.pem" +` +var invalidHttpTlsConfigFileNoClientCert = ` +httpUrl = "https:localhost:9101" +tlsMode = "STRICT" +tlsRootCA = "mydir/rootca.cert.pem" +` +var invalidHttpTlsConfigFileOnlyClientCert = ` +httpUrl = "https:localhost:9101" +tlsMode = "STRICT" +tlsRootCA = "mydir/rootca.cert.pem" +tlsClientCert = "mydir/client.cert.pem" +` +var invalidHttpTlsConfigFileOnlyClientKey = ` +httpUrl = "https:localhost:9101" +tlsMode = "STRICT" +tlsRootCA = "mydir/rootca.cert.pem" +tlsClientKey = "mydir/client.key.pem" +` +var httpTlsConfigFileWithHTTPOnly = ` +httpUrl = "http:localhost:9101" +tlsMode = "strict" +tlsRootCA = "mydir/rootca.cert.pem" +tlsClientCert = "mydir/client.cert.pem" +tlsClientKey = "mydir/client.key.pem" +` +var invalidConfigWithSocketAndHttp = ` +socket = "tm.ipc" +workdir = "qdata/c1" +httpUrl = "http:localhost:9101" +` +var invalidConfigWithNoSocketOrHttp = ` +` + +func TestDefaultTimeoutsUsedWhenNoConfigFileSpecified(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("this test case is not supported for windows") + } + + socketFile := filepath.Join(os.TempDir(), "socket-file.ipc") + syscall.Unlink(socketFile) + l, err := net.Listen("unix", socketFile) + if err != nil { + t.Fatalf("Could not create socket file '%v' for unit test, error: %v", socketFile, err) + } + defer l.Close() + + cfg, err := FetchConfig(socketFile) + if assert.NoError(t, err, "Failed to retrieve socket configuration") { + assert.True(t, IsSocketConfigured(cfg), "IsSocketConfigured() returned false, when expecting true") + assert.Equal(t, socketFile, filepath.Join(cfg.WorkDir, cfg.Socket), "Socket path unexpectedly changed when loading default config") + assert.Equal(t, DefaultConfig.DialTimeout, cfg.DialTimeout, "Did not get expected socket default DialTimeout") + assert.Equal(t, DefaultConfig.Timeout, cfg.Timeout, "Did not get expected socket default Timeout") + } + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestLoadSocketConfigWithTimeouts(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "socketConfigFileWithTimeouts.toml") + if err := ioutil.WriteFile(configFile, []byte(socketConfigFileWithTimeouts), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + if assert.NoError(t, err, "Failed to load config file") { + assert.True(t, IsSocketConfigured(cfg), "IsSocketConfigured() returned false, when expecting true") + assert.Equal(t, "qdata/c1/tm.ipc", filepath.Join(cfg.WorkDir, cfg.Socket), "Did not get expected socket path from config file") + assert.Equal(t, uint(8), cfg.DialTimeout, "Did not get expected socket DialTimeout from config file") + assert.Equal(t, uint(9), cfg.Timeout, "Did not get expected socket Timeout from config file") + } + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestLoadSocketConfigWithDefaultTimeouts(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "socketConfigFileNoTimeouts.toml") + if err := ioutil.WriteFile(configFile, []byte(socketConfigFileNoTimeouts), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + if assert.NoError(t, err, "Failed to load config file") { + assert.True(t, IsSocketConfigured(cfg), "IsSocketConfigured() returned false, when expecting true") + assert.Equal(t, DefaultConfig.DialTimeout, cfg.DialTimeout, "Did not get expected socket default DialTimeout from config file") + assert.Equal(t, DefaultConfig.Timeout, cfg.Timeout, "Did not get expected socket default Timeout from config file") + } + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestLoadHttpConfigWithTimeouts(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "httpConfigFileWithTimeouts.toml") + if err := ioutil.WriteFile(configFile, []byte(httpConfigFileWithTimeouts), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + if assert.NoError(t, err, "Failed to load config file") { + assert.False(t, IsSocketConfigured(cfg), "IsSocketConfigured() returned true, when expecting false") + assert.Equal(t, "http:localhost:9101", cfg.HttpUrl, "Did not get expected http url from config file") + assert.Equal(t, cfg.TlsMode, TlsOff, "Did not get expected IsTlsConfigured() value from config file") + assert.Equal(t, uint(101), cfg.Timeout, "Did not get expected http Timeout from config file") + assert.Equal(t, uint(102), cfg.HttpIdleConnTimeout, "Did not get expected http HttpIdleConnTimeout from config file") + assert.Equal(t, int(1001), cfg.HttpWriteBufferSize, "Did not get expected http HttpWriteBufferSize from config file") + assert.Equal(t, int(1002), cfg.HttpReadBufferSize, "Did not get expected http HttpReadBufferSize from config file") + } + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestLoadHttpConfigWithInvalidTls(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "httpConfigFileWithInvalidTlsMode.toml") + if err := ioutil.WriteFile(configFile, []byte(httpConfigFileWithInvalidTlsMode), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "invalid value for TLS mode in config file, must be either OFF or STRICT") + } +} + +func TestLoadHttpTlsConfigWithTimeouts(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "httpTlsConfigFileWithTimeouts.toml") + if err := ioutil.WriteFile(configFile, []byte(httpTlsConfigFileWithTimeouts), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + if assert.NoError(t, err, "Failed to load config file") { + assert.False(t, IsSocketConfigured(cfg), "IsSocketConfigured() returned true, when expecting false") + assert.Equal(t, "https:localhost:9101", cfg.HttpUrl, "Did not get expected http url from config file") + assert.Equal(t, cfg.TlsMode, TlsStrict, "Did not get expected IsTlsConfigured() value from config file") + assert.Equal(t, "mydir/rootca.cert.pem", cfg.TlsRootCA, "Did not get expected TlsRootCA from config file") + assert.Equal(t, "mydir/client.cert.pem", cfg.TlsClientCert, "Did not get expected TlsClientCert from config file") + assert.Equal(t, "mydir/client.key.pem", cfg.TlsClientKey, "Did not get expected TlsClientKey from config file") + assert.Equal(t, uint(101), cfg.Timeout, "Did not get expected http Timeout from config file") + assert.Equal(t, uint(102), cfg.HttpIdleConnTimeout, "Did not get expected http HttpIdleConnTimeout from config file") + assert.Equal(t, int(1001), cfg.HttpWriteBufferSize, "Did not get expected http HttpWriteBufferSize from config file") + assert.Equal(t, int(1002), cfg.HttpReadBufferSize, "Did not get expected http HttpReadBufferSize from config file") + assert.False(t, cfg.TlsInsecureSkipVerify, "Did not get expected TlsInsecureSkipVerify value from config file") + } + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestLoadHttpConfigWithDefaultTimeouts(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "httpTlsConfigFileNoTimeouts.toml") + if err := ioutil.WriteFile(configFile, []byte(httpTlsConfigFileNoTimeouts), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + if assert.NoError(t, err, "Failed to load config file") { + assert.False(t, IsSocketConfigured(cfg), "IsSocketConfigured() returned true, when expecting false") + assert.Equal(t, "https:localhost:9101", cfg.HttpUrl, "Did not get expected http url from config file") + assert.Equal(t, DefaultConfig.Timeout, cfg.Timeout, "Did not get expected http Timeout from config file") + assert.True(t, cfg.TlsInsecureSkipVerify, "Did not get expected TlsInsecureSkipVerify value from config file") + } + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestHTTPTlsWithBlankRootCert(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "httpTlsConfigFileNoRootCert.toml") + if err := ioutil.WriteFile(configFile, []byte(httpTlsConfigFileNoRootCert), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestHTTPTlsMissingClientCerts(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "invalidHttpTlsConfigFileNoClientCert.toml") + if err := ioutil.WriteFile(configFile, []byte(invalidHttpTlsConfigFileNoClientCert), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + assert.NoError(t, err) +} + +func TestHTTPTlsMissingOnlyClientKey(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "invalidHttpTlsConfigFileOnlyClientCert.toml") + if err := ioutil.WriteFile(configFile, []byte(invalidHttpTlsConfigFileOnlyClientCert), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "invalid details for HTTP connection with TLS, configuration must specify both clientCert and clientKey, or neither one") + } +} + +func TestHTTPTlsMissingOnlyClientCert(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "invalidHttpTlsConfigFileOnlyClientKey.toml") + if err := ioutil.WriteFile(configFile, []byte(invalidHttpTlsConfigFileOnlyClientKey), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "invalid details for HTTP connection with TLS, configuration must specify both clientCert and clientKey, or neither one") + } +} + +func TestTlsWithHTTPUrlOnly(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "httpTlsConfigFileWithHTTPOnly.toml") + if err := ioutil.WriteFile(configFile, []byte(httpTlsConfigFileWithHTTPOnly), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "connection is configured with TLS but HTTPS url is not specified") + } +} + +func TestSocketWithHTTPNotAllowed(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "invalidConfigWithSocketAndHttp.toml") + if err := ioutil.WriteFile(configFile, []byte(invalidConfigWithSocketAndHttp), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + cfg, err := FetchConfig(configFile) + assert.NoError(t, err) + + err = cfg.Validate() + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "HTTP URL and unix ipc file cannot both be specified for private transaction manager connection") + } +} + +func TestEitherSocketOrHTTPMustBeSpecified(t *testing.T) { + configFile := filepath.Join(os.TempDir(), "invalidConfigWithNoSocketOrHttp.toml") + if err := ioutil.WriteFile(configFile, []byte(invalidConfigWithNoSocketOrHttp), 0600); err != nil { + t.Fatalf("Failed to create config file for unit test, error: %v", err) + } + defer os.Remove(configFile) + + _, err := FetchConfig(configFile) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "either Socket or HTTP connection must be specified in config file") + } +} diff --git a/common/http/transport.go b/common/http/transport.go new file mode 100644 index 0000000000..593b78767d --- /dev/null +++ b/common/http/transport.go @@ -0,0 +1,55 @@ +package http + +import ( + "crypto/tls" + "fmt" + "net/http" + "path/filepath" + "time" + + "github.com/tv42/httpunix" +) + +func unixTransport(cfg Config) *httpunix.Transport { + // Note that clientTimeout doesn't work when using httpunix.Transport, so we set ResponseHeaderTimeout instead + t := &httpunix.Transport{ + DialTimeout: time.Duration(cfg.DialTimeout) * time.Second, + RequestTimeout: 5 * time.Second, + ResponseHeaderTimeout: time.Duration(cfg.Timeout) * time.Second, + } + t.RegisterLocation("c", filepath.Join(cfg.WorkDir, cfg.Socket)) + return t +} + +func httpTransport(cfg Config) *http.Transport { + t := &http.Transport{ + IdleConnTimeout: time.Duration(cfg.HttpIdleConnTimeout) * time.Second, + WriteBufferSize: cfg.HttpWriteBufferSize, + ReadBufferSize: cfg.HttpReadBufferSize, + } + return t +} + +func newTLSConfig(cfg Config) (*tls.Config, error) { + rootCAPool, err := loadRootCaCerts(cfg.TlsRootCA) + if err != nil { + return nil, err + } + + var getClientCertFunc func(*tls.CertificateRequestInfo) (*tls.Certificate, error) = nil + if len(cfg.TlsClientCert) != 0 && len(cfg.TlsClientKey) != 0 { + getClientCertFunc = func(info *tls.CertificateRequestInfo) (certificate *tls.Certificate, e error) { + c, err := tls.LoadX509KeyPair(cfg.TlsClientCert, cfg.TlsClientKey) + if err != nil { + return nil, fmt.Errorf("failed to load client key pair from '%v', '%v': %v", cfg.TlsClientCert, cfg.TlsClientKey, err) + } + return &c, nil + } + } + + return &tls.Config{ + RootCAs: rootCAPool, + InsecureSkipVerify: cfg.TlsInsecureSkipVerify, + GetClientCertificate: getClientCertFunc, + }, nil +} diff --git a/common/slice.go b/common/slice.go new file mode 100644 index 0000000000..a75f7c6485 --- /dev/null +++ b/common/slice.go @@ -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 . + +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 +} diff --git a/common/slice_test.go b/common/slice_test.go new file mode 100644 index 0000000000..88a63e7819 --- /dev/null +++ b/common/slice_test.go @@ -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 . + +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...)) +} diff --git a/common/types.go b/common/types.go index cdcc6c20ad..fb94b29362 100644 --- a/common/types.go +++ b/common/types.go @@ -18,6 +18,7 @@ package common import ( "database/sql/driver" + "encoding/base64" "encoding/hex" "encoding/json" "errors" @@ -37,13 +38,77 @@ const ( HashLength = 32 // AddressLength is the expected length of the address AddressLength = 20 + // length of the hash returned by Private Transaction Manager + EncryptedPayloadHashLength = 64 ) 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{}) ) +// Hash, returned by Private Transaction Manager, represents the 64-byte hash of encrypted payload +type EncryptedPayloadHash [EncryptedPayloadHashLength]byte + +// Using map to enable fast lookup +type EncryptedPayloadHashes map[EncryptedPayloadHash]struct{} + +// BytesToEncryptedPayloadHash sets b to EncryptedPayloadHash. +// If b is larger than len(h), b will be cropped from the left. +func BytesToEncryptedPayloadHash(b []byte) EncryptedPayloadHash { + var h EncryptedPayloadHash + h.SetBytes(b) + return h +} + +func Base64ToEncryptedPayloadHash(b64 string) (EncryptedPayloadHash, error) { + bytes, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + return EncryptedPayloadHash{}, fmt.Errorf("unable to convert base64 string %s to EncryptedPayloadHash. Cause: %v", b64, err) + } + return BytesToEncryptedPayloadHash(bytes), nil +} + +func (eph *EncryptedPayloadHash) SetBytes(b []byte) { + if len(b) > len(eph) { + b = b[len(b)-EncryptedPayloadHashLength:] + } + + copy(eph[EncryptedPayloadHashLength-len(b):], b) +} + +func (eph EncryptedPayloadHash) Hex() string { + return hexutil.Encode(eph[:]) +} + +func (eph EncryptedPayloadHash) Bytes() []byte { + return eph[:] +} + +func (eph EncryptedPayloadHash) String() string { + return eph.Hex() +} + +func (eph EncryptedPayloadHash) ToBase64() string { + return base64.StdEncoding.EncodeToString(eph[:]) +} + +func (eph EncryptedPayloadHash) TerminalString() string { + return fmt.Sprintf("%x…%x", eph[:3], eph[EncryptedPayloadHashLength-3:]) +} + +func (eph EncryptedPayloadHash) BytesTypeRef() *hexutil.Bytes { + b := hexutil.Bytes(eph.Bytes()) + return &b +} + +func EmptyEncryptedPayloadHash(eph EncryptedPayloadHash) bool { + return eph == EncryptedPayloadHash{} +} + // Hash represents the 32 byte Keccak256 hash of arbitrary data. type Hash [HashLength]byte @@ -55,6 +120,8 @@ func BytesToHash(b []byte) Hash { return h } +func StringToHash(s string) Hash { return BytesToHash([]byte(s)) } // dep: Istanbul + // BigToHash sets byte representation of b to hash. // If b is larger than len(h), b will be cropped from the left. func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } @@ -115,6 +182,10 @@ func (h *Hash) SetBytes(b []byte) { copy(h[HashLength-len(b):], b) } +func EmptyHash(h Hash) bool { + return h == Hash{} +} + // Generate implements testing/quick.Generator. func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { m := rand.Intn(len(h)) @@ -124,6 +195,23 @@ func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { return reflect.ValueOf(h) } +func (h Hash) ToBase64() string { + return base64.StdEncoding.EncodeToString(h.Bytes()) +} + +// Decode base64 string to Hash +// if String is empty then return empty hash +func Base64ToHash(b64 string) (Hash, error) { + if b64 == "" { + return Hash{}, nil + } + bytes, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + return Hash{}, fmt.Errorf("unable to convert base64 string %s to Hash. Cause: %v", b64, err) + } + return BytesToHash(bytes), nil +} + // Scan implements Scanner for database/sql. func (h *Hash) Scan(src interface{}) error { srcB, ok := src.([]byte) @@ -170,6 +258,48 @@ func (h UnprefixedHash) MarshalText() ([]byte, error) { return []byte(hex.EncodeToString(h[:])), nil } +func (ephs EncryptedPayloadHashes) ToBase64s() []string { + a := make([]string, 0, len(ephs)) + for eph := range ephs { + a = append(a, eph.ToBase64()) + } + return a +} + +func (ephs EncryptedPayloadHashes) NotExist(eph EncryptedPayloadHash) bool { + _, ok := ephs[eph] + return !ok +} + +func (ephs EncryptedPayloadHashes) Add(eph EncryptedPayloadHash) { + ephs[eph] = struct{}{} +} + +func Base64sToEncryptedPayloadHashes(b64s []string) (EncryptedPayloadHashes, error) { + ephs := make(EncryptedPayloadHashes) + for _, b64 := range b64s { + data, err := Base64ToEncryptedPayloadHash(b64) + if err != nil { + return nil, err + } + ephs.Add(data) + } + return ephs, nil +} + +// Print hex but only first 3 and last 3 bytes +func FormatTerminalString(data []byte) string { + l := len(data) + if l > 0 { + if l > 6 { + return fmt.Sprintf("%x…%x", data[:3], data[l-3:]) + } else { + return fmt.Sprintf("%x", data[:]) + } + } + return "" +} + /////////// Address // Address represents the 20 byte address of an Ethereum account. @@ -183,6 +313,8 @@ func BytesToAddress(b []byte) Address { return a } +func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } // dep: Istanbul + // BigToAddress returns Address with byte values of b. // If b is larger than len(h), b will be cropped from the left. func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } @@ -368,3 +500,12 @@ func (ma *MixedcaseAddress) ValidChecksum() bool { func (ma *MixedcaseAddress) Original() string { return ma.original } + +type DecryptRequest struct { + SenderKey []byte `json:"senderKey"` + CipherText []byte `json:"cipherText"` + CipherTextNonce []byte `json:"cipherTextNonce"` + RecipientBoxes []string `json:"recipientBoxes"` + RecipientNonce []byte `json:"recipientNonce"` + RecipientKeys []string `json:"recipientKeys"` +} diff --git a/common/types_test.go b/common/types_test.go index fffd673c6e..83df78a477 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -18,11 +18,14 @@ package common import ( "database/sql/driver" + "encoding/base64" "encoding/json" "math/big" "reflect" "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestBytesConversion(t *testing.T) { @@ -195,6 +198,68 @@ func TestMixedcaseAccount_Address(t *testing.T) { } +func TestBytesToEncryptedPayloadHash_whenTypical(t *testing.T) { + arbitraryBytes := []byte{10} + var expected EncryptedPayloadHash + expected[EncryptedPayloadHashLength-1] = 10 + + actual := BytesToEncryptedPayloadHash(arbitraryBytes) + + assert.Equal(t, expected, actual) +} + +func TestEncryptedPayloadHash_Bytes(t *testing.T) { + arbitraryBytes := []byte{10} + h := BytesToEncryptedPayloadHash(arbitraryBytes) + + actual := h.Bytes() + + assert.Equal(t, arbitraryBytes[0], actual[EncryptedPayloadHashLength-1]) +} + +func TestEncryptedPayloadHash_BytesTypeRef(t *testing.T) { + arbitraryBytes := []byte{10} + h := BytesToEncryptedPayloadHash(arbitraryBytes) + expected := h.Hex() + + bt := h.BytesTypeRef() + actual := bt.String() + + assert.Equal(t, expected, actual) +} + +func TestEncryptedPayloadHash_ToBase64(t *testing.T) { + arbitraryBytes := []byte{10} + h := BytesToEncryptedPayloadHash(arbitraryBytes) + expected := base64.StdEncoding.EncodeToString(h.Bytes()) + + actual := h.ToBase64() + + assert.Equal(t, expected, actual) +} + +func TestEmptyEncryptedPayloadHash(t *testing.T) { + + emptyHash := EncryptedPayloadHash{} + + assert.True(t, EmptyEncryptedPayloadHash(emptyHash)) +} + +func TestEncryptedPayloadHashes_whenTypical(t *testing.T) { + arbitraryBytes1 := []byte{10} + arbitraryBytes2 := []byte{5} + h, err := Base64sToEncryptedPayloadHashes([]string{base64.StdEncoding.EncodeToString(arbitraryBytes1), base64.StdEncoding.EncodeToString(arbitraryBytes2)}) + if err != nil { + t.Fatalf("must be able to convert but fail due to %s", err) + } + + arbitraryBytes3 := []byte{7} + newItem := BytesToEncryptedPayloadHash(arbitraryBytes3) + h.Add(newItem) + + assert.False(t, h.NotExist(newItem)) +} + func TestHash_Scan(t *testing.T) { type args struct { src interface{} @@ -371,3 +436,15 @@ func TestAddress_Value(t *testing.T) { }) } } + +func TestFormatTerminalString_Value(t *testing.T) { + assert.Equal(t, "", FormatTerminalString(nil)) + assert.Equal(t, "", FormatTerminalString([]byte{})) + b := []byte{ + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, + } + str := FormatTerminalString(b) + assert.Equal(t, "123456…90abcd", str) + str = FormatTerminalString(b[1:]) + assert.Equal(t, "34567890abcd", str) +} diff --git a/consensus/clique/api.go b/consensus/clique/api.go index 13d404d2c8..a5d05af434 100644 --- a/consensus/clique/api.go +++ b/consensus/clique/api.go @@ -17,6 +17,7 @@ package clique import ( + "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -120,7 +121,7 @@ func (api *API) Discard(address common.Address) { delete(api.clique.proposals, address) } -type status struct { +type Status struct { InturnPercent float64 `json:"inturnPercent"` SigningStatus map[common.Address]int `json:"sealerActivity"` NumBlocks uint64 `json:"numBlocks"` @@ -130,21 +131,50 @@ type status struct { // - the number of active signers, // - the number of signers, // - the percentage of in-turn blocks -func (api *API) Status() (*status, error) { +func (api *API) Status(startBlockNum *rpc.BlockNumber, endBlockNum *rpc.BlockNumber) (*Status, error) { var ( - numBlocks = uint64(64) - header = api.chain.CurrentHeader() + numBlocks uint64 + header *types.Header diff = uint64(0) optimals = 0 + + start uint64 + end uint64 ) - snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if startBlockNum != nil && endBlockNum == nil { + return nil, errors.New("pass the end block number") + } + + if startBlockNum == nil && endBlockNum != nil { + return nil, errors.New("pass the start block number") + } + + if startBlockNum == nil && endBlockNum == nil { + numBlocks = uint64(64) + header = api.chain.CurrentHeader() + end = header.Number.Uint64() + start = end - numBlocks + } else { + end = uint64(*endBlockNum) + start = uint64(*startBlockNum) + if start > end { + return nil, errors.New("start block number should be less than end block number") + } + + if end > api.chain.CurrentHeader().Number.Uint64() { + return nil, errors.New("end block number should be less than or equal to current block height") + } + + numBlocks = end - start + header = api.chain.GetHeaderByNumber(end) + } + + snap, err := api.clique.snapshot(api.chain, end, header.Hash(), nil) if err != nil { return nil, err } var ( signers = snap.signers() - end = header.Number.Uint64() - start = end - numBlocks ) if numBlocks > end { start = 1 @@ -169,7 +199,7 @@ func (api *API) Status() (*status, error) { } signStatus[sealer]++ } - return &status{ + return &Status{ InturnPercent: float64(100*optimals) / float64(numBlocks), SigningStatus: signStatus, NumBlocks: numBlocks, diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 35542baf4e..d2951f412f 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -248,8 +248,9 @@ func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, } number := header.Number.Uint64() - // Don't waste time checking blocks from the future - if header.Time > uint64(time.Now().Unix()) { + // Don't waste time checking blocks from the future (adjusting for allowed threshold) + adjustedTimeNow := time.Now().Add(time.Duration(c.config.AllowedFutureBlockTime) * time.Second).Unix() + if header.Time > uint64(adjustedTimeNow) { return consensus.ErrFutureBlock } // Checkpoint blocks need to enforce zero beneficiary @@ -369,7 +370,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo // at a checkpoint block without a parent (light client CHT), or we have piled // up more headers than allowed to be reorged (chain reinit from a freezer), // consider the checkpoint trusted and snapshot it. - if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) { + if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.GetImmutabilityThreshold() || chain.GetHeaderByNumber(number-1) == nil)) { checkpoint := chain.GetHeaderByNumber(number) if checkpoint != nil { hash := checkpoint.Hash() @@ -736,3 +737,8 @@ func encodeSigHeader(w io.Writer, header *types.Header) { panic("can't encode: " + err.Error()) } } + +// Protocol implements consensus.Engine.Protocol +func (c *Clique) Protocol() consensus.Protocol { + return consensus.CliqueProtocol +} diff --git a/consensus/consensus.go b/consensus/consensus.go index f753af550c..fd9ffdc23b 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -112,10 +113,25 @@ type Engine interface { // APIs returns the RPC APIs this consensus engine provides. APIs(chain ChainReader) []rpc.API + // Protocol returns the protocol for this consensus + Protocol() Protocol + // Close terminates any background threads maintained by the consensus engine. Close() error } +// Handler should be implemented is the consensus needs to handle and send peer's message +type Handler interface { + // NewChainHead handles a new head block comes + NewChainHead() error + + // HandleMsg handles a message from peer + HandleMsg(address common.Address, data p2p.Msg) (bool, error) + + // SetBroadcaster sets the broadcaster to send message to peers + SetBroadcaster(Broadcaster) +} + // PoW is a consensus engine based on proof-of-work. type PoW interface { Engine @@ -123,3 +139,14 @@ type PoW interface { // Hashrate returns the current mining hashrate of a PoW consensus engine. Hashrate() float64 } + +// Istanbul is a consensus engine to avoid byzantine failure +type Istanbul interface { + Engine + + // Start starts the engine + Start(chain ChainReader, currentBlock func() *types.Block, hasBadBlock func(hash common.Hash) bool) error + + // Stop stops the engine + Stop() error +} diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 8ae99b1994..b6b1914b1e 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -244,6 +244,11 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo // stock Ethereum ethash engine. // See YP section 4.3.4. "Block Header Validity" func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error { + // Quorum: ethash consensus is only used in raft for Quorum, skip verifyHeader + if chain != nil && chain.Config().IsQuorum { + return nil + } + // Ensure that the header's extra-data section is of a reasonable size if uint64(len(header.Extra)) > params.MaximumExtraDataSize { return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) @@ -278,9 +283,9 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent * if diff < 0 { diff *= -1 } - limit := parent.GasLimit / params.GasLimitBoundDivisor + limit := parent.GasLimit / params.OriginalGasLimitBoundDivisor - if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { + if uint64(diff) >= limit || header.GasLimit < params.OriginalMinGasLimit { return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) } // Verify that the block number is parent's +1 @@ -494,6 +499,11 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head // either using the usual ethash cache for it, or alternatively using a full DAG // to make remote mining fast. func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error { + // Quorum: ethash consensus is only used in raft for Quorum, skip verifySeal + if chain != nil && chain.Config().IsQuorum { + return nil + } + // If we're running a fake PoW, accept any seal as valid if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { time.Sleep(ethash.fakeDelay) @@ -642,3 +652,8 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header } state.AddBalance(header.Coinbase, reward) } + +// Quorum: wrapper for accumulateRewards to be called by raft minter +func AccumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { + accumulateRewards(config, state, header, uncles) +} diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 31bdceb4c3..dabade8043 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -638,6 +638,9 @@ func (ethash *Ethash) SetThreads(threads int) { // Note the returned hashrate includes local hashrate, but also includes the total // hashrate of all remote miner. func (ethash *Ethash) Hashrate() float64 { + if ethash.hashrate == nil { + return 0 + } // Short circuit if we are run the ethash in normal/test mode. if ethash.config.PowMode != ModeNormal && ethash.config.PowMode != ModeTest { return ethash.hashrate.Rate1() @@ -680,3 +683,8 @@ func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API { func SeedHash(block uint64) []byte { return seedHash(block) } + +// Protocol implements consensus.Engine.Protocol +func (ethash *Ethash) Protocol() consensus.Protocol { + return consensus.EthProtocol +} diff --git a/consensus/istanbul/backend.go b/consensus/istanbul/backend.go new file mode 100644 index 0000000000..22abed096e --- /dev/null +++ b/consensus/istanbul/backend.go @@ -0,0 +1,75 @@ +// Copyright 2017 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 . + +package istanbul + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" +) + +// Backend provides application specific functions for Istanbul core +type Backend interface { + // Address returns the owner's address + Address() common.Address + + // Validators returns the validator set + Validators(proposal Proposal) ValidatorSet + + // EventMux returns the event mux in backend + EventMux() *event.TypeMux + + // Broadcast sends a message to all validators (include self) + Broadcast(valSet ValidatorSet, payload []byte) error + + // Gossip sends a message to all validators (exclude self) + Gossip(valSet ValidatorSet, payload []byte) error + + // Commit delivers an approved proposal to backend. + // The delivered proposal will be put into blockchain. + Commit(proposal Proposal, seals [][]byte) error + + // Verify verifies the proposal. If a consensus.ErrFutureBlock error is returned, + // the time difference of the proposal and current time is also returned. + Verify(Proposal) (time.Duration, error) + + // Sign signs input data with the backend's private key + Sign([]byte) ([]byte, error) + + // CheckSignature verifies the signature by checking if it's signed by + // the given validator + CheckSignature(data []byte, addr common.Address, sig []byte) error + + // LastProposal retrieves latest committed proposal and the address of proposer + LastProposal() (Proposal, common.Address) + + // HasPropsal checks if the combination of the given hash and height matches any existing blocks + HasPropsal(hash common.Hash, number *big.Int) bool + + // GetProposer returns the proposer of the given block height + GetProposer(number uint64) common.Address + + // ParentValidators returns the validator set of the given proposal's parent block + ParentValidators(proposal Proposal) ValidatorSet + + // HasBadBlock returns whether the block with the hash is a bad block + HasBadProposal(hash common.Hash) bool + + Close() error +} diff --git a/consensus/istanbul/backend/api.go b/consensus/istanbul/backend/api.go new file mode 100644 index 0000000000..95560d592f --- /dev/null +++ b/consensus/istanbul/backend/api.go @@ -0,0 +1,272 @@ +// Copyright 2017 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 . + +package backend + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +// API is a user facing RPC API to dump Istanbul state +type API struct { + chain consensus.ChainReader + istanbul *backend +} + +// BlockSigners is contains who created and who signed a particular block, denoted by its number and hash +type BlockSigners struct { + Number uint64 + Hash common.Hash + Author common.Address + Committers []common.Address +} + +type Status struct { + SigningStatus map[common.Address]int `json:"sealerActivity"` + NumBlocks uint64 `json:"numBlocks"` +} + +// NodeAddress returns the public address that is used to sign block headers in IBFT +func (api *API) NodeAddress() common.Address { + return api.istanbul.Address() +} + +// GetSignersFromBlock returns the signers and minter for a given block number, or the +// latest block available if none is specified +func (api *API) GetSignersFromBlock(number *rpc.BlockNumber) (*BlockSigners, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + + if header == nil { + return nil, errUnknownBlock + } + + return api.signers(header) +} + +// GetSignersFromBlockByHash returns the signers and minter for a given block hash +func (api *API) GetSignersFromBlockByHash(hash common.Hash) (*BlockSigners, error) { + header := api.chain.GetHeaderByHash(hash) + if header == nil { + return nil, errUnknownBlock + } + + return api.signers(header) +} + +func (api *API) signers(header *types.Header) (*BlockSigners, error) { + author, err := api.istanbul.Author(header) + if err != nil { + return nil, err + } + + committers, err := api.istanbul.Signers(header) + if err != nil { + return nil, err + } + + return &BlockSigners{ + Number: header.Number.Uint64(), + Hash: header.Hash(), + Author: author, + Committers: committers, + }, nil +} + +// GetSnapshot retrieves the state snapshot at a given block. +func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return its snapshot + if header == nil { + return nil, errUnknownBlock + } + return api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) +} + +// GetSnapshotAtHash retrieves the state snapshot at a given block. +func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { + header := api.chain.GetHeaderByHash(hash) + if header == nil { + return nil, errUnknownBlock + } + return api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) +} + +// GetValidators retrieves the list of authorized validators at the specified block. +func (api *API) GetValidators(number *rpc.BlockNumber) ([]common.Address, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return the validators from its snapshot + if header == nil { + return nil, errUnknownBlock + } + snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.validators(), nil +} + +// GetValidatorsAtHash retrieves the state snapshot at a given block. +func (api *API) GetValidatorsAtHash(hash common.Hash) ([]common.Address, error) { + header := api.chain.GetHeaderByHash(hash) + if header == nil { + return nil, errUnknownBlock + } + snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.validators(), nil +} + +// Candidates returns the current candidates the node tries to uphold and vote on. +func (api *API) Candidates() map[common.Address]bool { + api.istanbul.candidatesLock.RLock() + defer api.istanbul.candidatesLock.RUnlock() + + proposals := make(map[common.Address]bool) + for address, auth := range api.istanbul.candidates { + proposals[address] = auth + } + return proposals +} + +// Propose injects a new authorization candidate that the validator will attempt to +// push through. +func (api *API) Propose(address common.Address, auth bool) { + api.istanbul.candidatesLock.Lock() + defer api.istanbul.candidatesLock.Unlock() + + api.istanbul.candidates[address] = auth +} + +// Discard drops a currently running candidate, stopping the validator from casting +// further votes (either for or against). +func (api *API) Discard(address common.Address) { + api.istanbul.candidatesLock.Lock() + defer api.istanbul.candidatesLock.Unlock() + + delete(api.istanbul.candidates, address) +} + +func (api *API) Status(startBlockNum *rpc.BlockNumber, endBlockNum *rpc.BlockNumber) (*Status, error) { + var ( + numBlocks uint64 + header = api.chain.CurrentHeader() + start uint64 + end uint64 + blockNumber rpc.BlockNumber + ) + if startBlockNum != nil && endBlockNum == nil { + return nil, errors.New("pass the end block number") + } + + if startBlockNum == nil && endBlockNum != nil { + return nil, errors.New("pass the start block number") + } + + if startBlockNum == nil && endBlockNum == nil { + numBlocks = uint64(64) + header = api.chain.CurrentHeader() + end = header.Number.Uint64() + start = end - numBlocks + blockNumber = rpc.BlockNumber(header.Number.Int64()) + } else { + end = uint64(*endBlockNum) + start = uint64(*startBlockNum) + if start > end { + return nil, errors.New("start block number should be less than end block number") + } + + if end > api.chain.CurrentHeader().Number.Uint64() { + return nil, errors.New("end block number should be less than or equal to current block height") + } + + numBlocks = end - start + header = api.chain.GetHeaderByNumber(end) + blockNumber = rpc.BlockNumber(end) + } + + signers, err := api.GetValidators(&blockNumber) + + if err != nil { + return nil, err + } + + if numBlocks >= end { + start = 1 + if end > start { + numBlocks = end - start + } else { + numBlocks = 0 + } + } + signStatus := make(map[common.Address]int) + for _, s := range signers { + signStatus[s] = 0 + } + + for n := start; n < end; n++ { + blockNum := rpc.BlockNumber(int64(n)) + s, _ := api.GetSignersFromBlock(&blockNum) + signStatus[s.Author]++ + + } + return &Status{ + SigningStatus: signStatus, + NumBlocks: numBlocks, + }, nil +} + +func (api *API) IsValidator(blockNum *rpc.BlockNumber) (bool, error) { + var blockNumber rpc.BlockNumber + if blockNum != nil { + blockNumber = *blockNum + } else { + header := api.chain.CurrentHeader() + blockNumber = rpc.BlockNumber(header.Number.Int64()) + } + s, _ := api.GetValidators(&blockNumber) + + for _, v := range s { + if v == api.istanbul.address { + return true, nil + } + } + return false, nil +} diff --git a/consensus/istanbul/backend/backend.go b/consensus/istanbul/backend/backend.go new file mode 100644 index 0000000000..5ac1b30068 --- /dev/null +++ b/consensus/istanbul/backend/backend.go @@ -0,0 +1,319 @@ +// Copyright 2017 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 . + +package backend + +import ( + "crypto/ecdsa" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/istanbul" + istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + lru "github.com/hashicorp/golang-lru" +) + +const ( + // fetcherID is the ID indicates the block is from Istanbul engine + fetcherID = "istanbul" +) + +// New creates an Ethereum backend for Istanbul core engine. +func New(config *istanbul.Config, privateKey *ecdsa.PrivateKey, db ethdb.Database) consensus.Istanbul { + // Allocate the snapshot caches and create the engine + recents, _ := lru.NewARC(inmemorySnapshots) + recentMessages, _ := lru.NewARC(inmemoryPeers) + knownMessages, _ := lru.NewARC(inmemoryMessages) + backend := &backend{ + config: config, + istanbulEventMux: new(event.TypeMux), + privateKey: privateKey, + address: crypto.PubkeyToAddress(privateKey.PublicKey), + logger: log.New(), + db: db, + commitCh: make(chan *types.Block, 1), + recents: recents, + candidates: make(map[common.Address]bool), + coreStarted: false, + recentMessages: recentMessages, + knownMessages: knownMessages, + } + backend.core = istanbulCore.New(backend, backend.config) + return backend +} + +// ---------------------------------------------------------------------------- + +type backend struct { + config *istanbul.Config + istanbulEventMux *event.TypeMux + privateKey *ecdsa.PrivateKey + address common.Address + core istanbulCore.Engine + logger log.Logger + db ethdb.Database + chain consensus.ChainReader + currentBlock func() *types.Block + hasBadBlock func(hash common.Hash) bool + + // the channels for istanbul engine notifications + commitCh chan *types.Block + proposedBlockHash common.Hash + sealMu sync.Mutex + coreStarted bool + coreMu sync.RWMutex + + // Current list of candidates we are pushing + candidates map[common.Address]bool + // Protects the signer fields + candidatesLock sync.RWMutex + // Snapshots for recent block to speed up reorgs + recents *lru.ARCCache + + // event subscription for ChainHeadEvent event + broadcaster consensus.Broadcaster + + recentMessages *lru.ARCCache // the cache of peer's messages + knownMessages *lru.ARCCache // the cache of self messages +} + +// zekun: HACK +func (sb *backend) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + return new(big.Int) +} + +// Address implements istanbul.Backend.Address +func (sb *backend) Address() common.Address { + return sb.address +} + +// Validators implements istanbul.Backend.Validators +func (sb *backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet { + return sb.getValidators(proposal.Number().Uint64(), proposal.Hash()) +} + +// Broadcast implements istanbul.Backend.Broadcast +func (sb *backend) Broadcast(valSet istanbul.ValidatorSet, payload []byte) error { + // send to others + sb.Gossip(valSet, payload) + // send to self + msg := istanbul.MessageEvent{ + Payload: payload, + } + go sb.istanbulEventMux.Post(msg) + return nil +} + +// Broadcast implements istanbul.Backend.Gossip +func (sb *backend) Gossip(valSet istanbul.ValidatorSet, payload []byte) error { + hash := istanbul.RLPHash(payload) + sb.knownMessages.Add(hash, true) + + targets := make(map[common.Address]bool) + for _, val := range valSet.List() { + if val.Address() != sb.Address() { + targets[val.Address()] = true + } + } + if sb.broadcaster != nil && len(targets) > 0 { + ps := sb.broadcaster.FindPeers(targets) + for addr, p := range ps { + ms, ok := sb.recentMessages.Get(addr) + var m *lru.ARCCache + if ok { + m, _ = ms.(*lru.ARCCache) + if _, k := m.Get(hash); k { + // This peer had this event, skip it + continue + } + } else { + m, _ = lru.NewARC(inmemoryMessages) + } + + m.Add(hash, true) + sb.recentMessages.Add(addr, m) + go p.SendConsensus(istanbulMsg, payload) + } + } + return nil +} + +// Commit implements istanbul.Backend.Commit +func (sb *backend) Commit(proposal istanbul.Proposal, seals [][]byte) error { + // Check if the proposal is a valid block + block := &types.Block{} + block, ok := proposal.(*types.Block) + if !ok { + sb.logger.Error("Invalid proposal, %v", proposal) + return errInvalidProposal + } + + h := block.Header() + // Append seals into extra-data + err := writeCommittedSeals(h, seals) + if err != nil { + return err + } + // update block's header + block = block.WithSeal(h) + + sb.logger.Info("Committed", "address", sb.Address(), "hash", proposal.Hash(), "number", proposal.Number().Uint64()) + // - if the proposed and committed blocks are the same, send the proposed hash + // to commit channel, which is being watched inside the engine.Seal() function. + // - otherwise, we try to insert the block. + // -- if success, the ChainHeadEvent event will be broadcasted, try to build + // the next block and the previous Seal() will be stopped. + // -- otherwise, a error will be returned and a round change event will be fired. + if sb.proposedBlockHash == block.Hash() { + // feed block hash to Seal() and wait the Seal() result + sb.commitCh <- block + return nil + } + + if sb.broadcaster != nil { + sb.broadcaster.Enqueue(fetcherID, block) + } + return nil +} + +// EventMux implements istanbul.Backend.EventMux +func (sb *backend) EventMux() *event.TypeMux { + return sb.istanbulEventMux +} + +// Verify implements istanbul.Backend.Verify +func (sb *backend) Verify(proposal istanbul.Proposal) (time.Duration, error) { + // Check if the proposal is a valid block + block := &types.Block{} + block, ok := proposal.(*types.Block) + if !ok { + sb.logger.Error("Invalid proposal, %v", proposal) + return 0, errInvalidProposal + } + + // check bad block + if sb.HasBadProposal(block.Hash()) { + return 0, core.ErrBlacklistedHash + } + + // check block body + txnHash := types.DeriveSha(block.Transactions()) + uncleHash := types.CalcUncleHash(block.Uncles()) + if txnHash != block.Header().TxHash { + return 0, errMismatchTxhashes + } + if uncleHash != nilUncleHash { + return 0, errInvalidUncleHash + } + + // verify the header of proposed block + err := sb.VerifyHeader(sb.chain, block.Header(), false) + // ignore errEmptyCommittedSeals error because we don't have the committed seals yet + if err == nil || err == errEmptyCommittedSeals { + return 0, nil + } else if err == consensus.ErrFutureBlock { + return time.Unix(int64(block.Header().Time), 0).Sub(now()), consensus.ErrFutureBlock + } + return 0, err +} + +// Sign implements istanbul.Backend.Sign +func (sb *backend) Sign(data []byte) ([]byte, error) { + hashData := crypto.Keccak256(data) + return crypto.Sign(hashData, sb.privateKey) +} + +// CheckSignature implements istanbul.Backend.CheckSignature +func (sb *backend) CheckSignature(data []byte, address common.Address, sig []byte) error { + signer, err := istanbul.GetSignatureAddress(data, sig) + if err != nil { + log.Error("Failed to get signer address", "err", err) + return err + } + // Compare derived addresses + if signer != address { + return errInvalidSignature + } + return nil +} + +// HasPropsal implements istanbul.Backend.HashBlock +func (sb *backend) HasPropsal(hash common.Hash, number *big.Int) bool { + return sb.chain.GetHeader(hash, number.Uint64()) != nil +} + +// GetProposer implements istanbul.Backend.GetProposer +func (sb *backend) GetProposer(number uint64) common.Address { + if h := sb.chain.GetHeaderByNumber(number); h != nil { + a, _ := sb.Author(h) + return a + } + return common.Address{} +} + +// ParentValidators implements istanbul.Backend.GetParentValidators +func (sb *backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet { + if block, ok := proposal.(*types.Block); ok { + return sb.getValidators(block.Number().Uint64()-1, block.ParentHash()) + } + return validator.NewSet(nil, sb.config.ProposerPolicy) +} + +func (sb *backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet { + snap, err := sb.snapshot(sb.chain, number, hash, nil) + if err != nil { + return validator.NewSet(nil, sb.config.ProposerPolicy) + } + return snap.ValSet +} + +func (sb *backend) LastProposal() (istanbul.Proposal, common.Address) { + block := sb.currentBlock() + + var proposer common.Address + if block.Number().Cmp(common.Big0) > 0 { + var err error + proposer, err = sb.Author(block.Header()) + if err != nil { + sb.logger.Error("Failed to get block proposer", "err", err) + return nil, common.Address{} + } + } + + // Return header only block here since we don't need block body + return block, proposer +} + +func (sb *backend) HasBadProposal(hash common.Hash) bool { + if sb.hasBadBlock == nil { + return false + } + return sb.hasBadBlock(hash) +} + +func (sb *backend) Close() error { + return nil +} diff --git a/consensus/istanbul/backend/backend_test.go b/consensus/istanbul/backend/backend_test.go new file mode 100644 index 0000000000..0f51ecd2e0 --- /dev/null +++ b/consensus/istanbul/backend/backend_test.go @@ -0,0 +1,239 @@ +// Copyright 2017 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 . + +package backend + +import ( + "bytes" + "crypto/ecdsa" + "sort" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestSign(t *testing.T) { + b := newBackend() + data := []byte("Here is a string....") + sig, err := b.Sign(data) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + //Check signature recover + hashData := crypto.Keccak256(data) + pubkey, _ := crypto.Ecrecover(hashData, sig) + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + if signer != getAddress() { + t.Errorf("address mismatch: have %v, want %s", signer.Hex(), getAddress().Hex()) + } +} + +func TestCheckSignature(t *testing.T) { + key, _ := generatePrivateKey() + data := []byte("Here is a string....") + hashData := crypto.Keccak256(data) + sig, _ := crypto.Sign(hashData, key) + b := newBackend() + a := getAddress() + err := b.CheckSignature(data, a, sig) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + a = getInvalidAddress() + err = b.CheckSignature(data, a, sig) + if err != errInvalidSignature { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature) + } +} + +func TestCheckValidatorSignature(t *testing.T) { + vset, keys := newTestValidatorSet(5) + + // 1. Positive test: sign with validator's key should succeed + data := []byte("dummy data") + hashData := crypto.Keccak256(data) + for i, k := range keys { + // Sign + sig, err := crypto.Sign(hashData, k) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + // CheckValidatorSignature should succeed + addr, err := istanbul.CheckValidatorSignature(vset, data, sig) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + validator := vset.GetByIndex(uint64(i)) + if addr != validator.Address() { + t.Errorf("validator address mismatch: have %v, want %v", addr, validator.Address()) + } + } + + // 2. Negative test: sign with any key other than validator's key should return error + key, err := crypto.GenerateKey() + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + // Sign + sig, err := crypto.Sign(hashData, key) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + // CheckValidatorSignature should return ErrUnauthorizedAddress + addr, err := istanbul.CheckValidatorSignature(vset, data, sig) + if err != istanbul.ErrUnauthorizedAddress { + t.Errorf("error mismatch: have %v, want %v", err, istanbul.ErrUnauthorizedAddress) + } + emptyAddr := common.Address{} + if addr != emptyAddr { + t.Errorf("address mismatch: have %v, want %v", addr, emptyAddr) + } +} + +func TestCommit(t *testing.T) { + backend := newBackend() + + commitCh := make(chan *types.Block) + // Case: it's a proposer, so the backend.commit will receive channel result from backend.Commit function + testCases := []struct { + expectedErr error + expectedSignature [][]byte + expectedBlock func() *types.Block + }{ + { + // normal case + nil, + [][]byte{append([]byte{1}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-1)...)}, + func() *types.Block { + chain, engine := newBlockChain(1) + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block) + return expectedBlock + }, + }, + { + // invalid signature + errInvalidCommittedSeals, + nil, + func() *types.Block { + chain, engine := newBlockChain(1) + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block) + return expectedBlock + }, + }, + } + + for _, test := range testCases { + expBlock := test.expectedBlock() + go func() { + result := <-backend.commitCh + commitCh <- result + }() + + backend.proposedBlockHash = expBlock.Hash() + if err := backend.Commit(expBlock, test.expectedSignature); err != nil { + if err != test.expectedErr { + t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) + } + } + + if test.expectedErr == nil { + // to avoid race condition is occurred by goroutine + select { + case result := <-commitCh: + if result.Hash() != expBlock.Hash() { + t.Errorf("hash mismatch: have %v, want %v", result.Hash(), expBlock.Hash()) + } + case <-time.After(10 * time.Second): + t.Fatal("timeout") + } + } + } +} + +func TestGetProposer(t *testing.T) { + chain, engine := newBlockChain(1) + block := makeBlock(chain, engine, chain.Genesis()) + chain.InsertChain(types.Blocks{block}) + expected := engine.GetProposer(1) + actual := engine.Address() + if actual != expected { + t.Errorf("proposer mismatch: have %v, want %v", actual.Hex(), expected.Hex()) + } +} + +/** + * SimpleBackend + * Private key: bb047e5940b6d83354d9432db7c449ac8fca2248008aaa7271369880f9f11cc1 + * Public key: 04a2bfb0f7da9e1b9c0c64e14f87e8fb82eb0144e97c25fe3a977a921041a50976984d18257d2495e7bfd3d4b280220217f429287d25ecdf2b0d7c0f7aae9aa624 + * Address: 0x70524d664ffe731100208a0154e556f9bb679ae6 + */ +func getAddress() common.Address { + return common.HexToAddress("0x70524d664ffe731100208a0154e556f9bb679ae6") +} + +func getInvalidAddress() common.Address { + return common.HexToAddress("0x9535b2e7faaba5288511d89341d94a38063a349b") +} + +func generatePrivateKey() (*ecdsa.PrivateKey, error) { + key := "bb047e5940b6d83354d9432db7c449ac8fca2248008aaa7271369880f9f11cc1" + return crypto.HexToECDSA(key) +} + +func newTestValidatorSet(n int) (istanbul.ValidatorSet, []*ecdsa.PrivateKey) { + // generate validators + keys := make(Keys, n) + addrs := make([]common.Address, n) + for i := 0; i < n; i++ { + privateKey, _ := crypto.GenerateKey() + keys[i] = privateKey + addrs[i] = crypto.PubkeyToAddress(privateKey.PublicKey) + } + vset := validator.NewSet(addrs, istanbul.RoundRobin) + sort.Sort(keys) //Keys need to be sorted by its public key address + return vset, keys +} + +type Keys []*ecdsa.PrivateKey + +func (slice Keys) Len() int { + return len(slice) +} + +func (slice Keys) Less(i, j int) bool { + return strings.Compare(crypto.PubkeyToAddress(slice[i].PublicKey).String(), crypto.PubkeyToAddress(slice[j].PublicKey).String()) < 0 +} + +func (slice Keys) Swap(i, j int) { + slice[i], slice[j] = slice[j], slice[i] +} + +func newBackend() (b *backend) { + _, b = newBlockChain(4) + key, _ := generatePrivateKey() + b.privateKey = key + return +} diff --git a/consensus/istanbul/backend/engine.go b/consensus/istanbul/backend/engine.go new file mode 100644 index 0000000000..b9df67bc71 --- /dev/null +++ b/consensus/istanbul/backend/engine.go @@ -0,0 +1,758 @@ +// Copyright 2017 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 . + +package backend + +import ( + "bytes" + "errors" + "math/big" + "math/rand" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/istanbul" + istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + lru "github.com/hashicorp/golang-lru" + "golang.org/x/crypto/sha3" +) + +const ( + checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + inmemoryPeers = 40 + inmemoryMessages = 1024 +) + +var ( + // errInvalidProposal is returned when a prposal is malformed. + errInvalidProposal = errors.New("invalid proposal") + // errInvalidSignature is returned when given signature is not signed by given + // address. + errInvalidSignature = errors.New("invalid signature") + // errUnknownBlock is returned when the list of validators is requested for a block + // that is not part of the local blockchain. + errUnknownBlock = errors.New("unknown block") + // errUnauthorized is returned if a header is signed by a non authorized entity. + errUnauthorized = errors.New("unauthorized") + // errInvalidDifficulty is returned if the difficulty of a block is not 1 + errInvalidDifficulty = errors.New("invalid difficulty") + // errInvalidExtraDataFormat is returned when the extra data format is incorrect + errInvalidExtraDataFormat = errors.New("invalid extra data format") + // errInvalidMixDigest is returned if a block's mix digest is not Istanbul digest. + errInvalidMixDigest = errors.New("invalid Istanbul mix digest") + // errInvalidNonce is returned if a block's nonce is invalid + errInvalidNonce = errors.New("invalid nonce") + // errInvalidUncleHash is returned if a block contains an non-empty uncle list. + errInvalidUncleHash = errors.New("non empty uncle hash") + // errInconsistentValidatorSet is returned if the validator set is inconsistent + // errInconsistentValidatorSet = errors.New("non empty uncle hash") + // errInvalidTimestamp is returned if the timestamp of a block is lower than the previous block's timestamp + the minimum block period. + errInvalidTimestamp = errors.New("invalid timestamp") + // errInvalidVotingChain is returned if an authorization list is attempted to + // be modified via out-of-range or non-contiguous headers. + errInvalidVotingChain = errors.New("invalid voting chain") + // errInvalidVote is returned if a nonce value is something else that the two + // allowed constants of 0x00..0 or 0xff..f. + errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f") + // errInvalidCommittedSeals is returned if the committed seal is not signed by any of parent validators. + errInvalidCommittedSeals = errors.New("invalid committed seals") + // errEmptyCommittedSeals is returned if the field of committed seals is zero. + errEmptyCommittedSeals = errors.New("zero committed seals") + // errMismatchTxhashes is returned if the TxHash in header is mismatch. + errMismatchTxhashes = errors.New("mismatch transactions hashes") +) +var ( + defaultDifficulty = big.NewInt(1) + nilUncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. + emptyNonce = types.BlockNonce{} + now = time.Now + + nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new validator + nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a validator. + + inmemoryAddresses = 20 // Number of recent addresses from ecrecover + recentAddresses, _ = lru.NewARC(inmemoryAddresses) +) + +// Author retrieves the Ethereum address of the account that minted the given +// block, which may be different from the header's coinbase if a consensus +// engine is based on signatures. +func (sb *backend) Author(header *types.Header) (common.Address, error) { + return ecrecover(header) +} + +// Signers extracts all the addresses who have signed the given header +// It will extract for each seal who signed it, regardless of if the seal is +// repeated +func (sb *backend) Signers(header *types.Header) ([]common.Address, error) { + extra, err := types.ExtractIstanbulExtra(header) + if err != nil { + return []common.Address{}, err + } + + var addrs []common.Address + proposalSeal := istanbulCore.PrepareCommittedSeal(header.Hash()) + + // 1. Get committed seals from current header + for _, seal := range extra.CommittedSeal { + // 2. Get the original address by seal and parent block hash + addr, err := istanbul.GetSignatureAddress(proposalSeal, seal) + if err != nil { + sb.logger.Error("not a valid address", "err", err) + return nil, errInvalidSignature + } + addrs = append(addrs, addr) + } + return addrs, nil +} + +// VerifyHeader checks whether a header conforms to the consensus rules of a +// given engine. Verifying the seal may be done optionally here, or explicitly +// via the VerifySeal method. +func (sb *backend) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { + return sb.verifyHeader(chain, header, nil) +} + +// verifyHeader checks whether a header conforms to the consensus rules.The +// caller may optionally pass in a batch of parents (ascending order) to avoid +// looking those up from the database. This is useful for concurrently verifying +// a batch of new headers. +func (sb *backend) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { + if header.Number == nil { + return errUnknownBlock + } + + // Don't waste time checking blocks from the future (adjusting for allowed threshold) + adjustedTimeNow := now().Add(time.Duration(sb.config.AllowedFutureBlockTime) * time.Second).Unix() + if header.Time > uint64(adjustedTimeNow) { + return consensus.ErrFutureBlock + } + + // Ensure that the extra data format is satisfied + if _, err := types.ExtractIstanbulExtra(header); err != nil { + return errInvalidExtraDataFormat + } + + // Ensure that the coinbase is valid + if header.Nonce != (emptyNonce) && !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) { + return errInvalidNonce + } + // Ensure that the mix digest is zero as we don't have fork protection currently + if header.MixDigest != types.IstanbulDigest { + return errInvalidMixDigest + } + // Ensure that the block doesn't contain any uncles which are meaningless in Istanbul + if header.UncleHash != nilUncleHash { + return errInvalidUncleHash + } + // Ensure that the block's difficulty is meaningful (may not be correct at this point) + if header.Difficulty == nil || header.Difficulty.Cmp(defaultDifficulty) != 0 { + return errInvalidDifficulty + } + + return sb.verifyCascadingFields(chain, header, parents) +} + +// verifyCascadingFields verifies all the header fields that are not standalone, +// rather depend on a batch of previous headers. The caller may optionally pass +// in a batch of parents (ascending order) to avoid looking those up from the +// database. This is useful for concurrently verifying a batch of new headers. +func (sb *backend) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { + // The genesis block is the always valid dead-end + number := header.Number.Uint64() + if number == 0 { + return nil + } + // Ensure that the block's timestamp isn't too close to it's parent + var parent *types.Header + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + if parent.Time+sb.config.BlockPeriod > header.Time { + return errInvalidTimestamp + } + // Verify validators in extraData. Validators in snapshot and extraData should be the same. + snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + validators := make([]byte, len(snap.validators())*common.AddressLength) + for i, validator := range snap.validators() { + copy(validators[i*common.AddressLength:], validator[:]) + } + if err := sb.verifySigner(chain, header, parents); err != nil { + return err + } + + return sb.verifyCommittedSeals(chain, header, parents) +} + +// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers +// concurrently. The method returns a quit channel to abort the operations and +// a results channel to retrieve the async verifications (the order is that of +// the input slice). +func (sb *backend) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + go func() { + errored := false + for i, header := range headers { + var err error + if errored { + err = consensus.ErrUnknownAncestor + } else { + err = sb.verifyHeader(chain, header, headers[:i]) + } + + if err != nil { + errored = true + } + + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// VerifyUncles verifies that the given block's uncles conform to the consensus +// rules of a given engine. +func (sb *backend) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + if len(block.Uncles()) > 0 { + return errInvalidUncleHash + } + return nil +} + +// verifySigner checks whether the signer is in parent's validator set +func (sb *backend) verifySigner(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { + // Verifying the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return errUnknownBlock + } + + // Retrieve the snapshot needed to verify this header and cache it + snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + // resolve the authorization key and check against signers + signer, err := ecrecover(header) + if err != nil { + return err + } + + // Signer should be in the validator set of previous block's extraData. + if _, v := snap.ValSet.GetByAddress(signer); v == nil { + return errUnauthorized + } + return nil +} + +// verifyCommittedSeals checks whether every committed seal is signed by one of the parent's validators +func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { + number := header.Number.Uint64() + // We don't need to verify committed seals in the genesis block + if number == 0 { + return nil + } + + // Retrieve the snapshot needed to verify this header and cache it + snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + extra, err := types.ExtractIstanbulExtra(header) + if err != nil { + return err + } + // The length of Committed seals should be larger than 0 + if len(extra.CommittedSeal) == 0 { + return errEmptyCommittedSeals + } + + validators := snap.ValSet.Copy() + // Check whether the committed seals are generated by parent's validators + validSeal := 0 + committers, err := sb.Signers(header) + if err != nil { + return err + } + for _, addr := range committers { + if validators.RemoveValidator(addr) { + validSeal++ + continue + } + return errInvalidCommittedSeals + } + + // The length of validSeal should be larger than number of faulty node + 1 + if validSeal <= snap.ValSet.F() { + return errInvalidCommittedSeals + } + + return nil +} + +// VerifySeal checks whether the crypto seal on a header is valid according to +// the consensus rules of the given engine. +func (sb *backend) VerifySeal(chain consensus.ChainReader, header *types.Header) error { + // get parent header and ensure the signer is in parent's validator set + number := header.Number.Uint64() + if number == 0 { + return errUnknownBlock + } + + // ensure that the difficulty equals to defaultDifficulty + if header.Difficulty.Cmp(defaultDifficulty) != 0 { + return errInvalidDifficulty + } + return sb.verifySigner(chain, header, nil) +} + +// Prepare initializes the consensus fields of a block header according to the +// rules of a particular engine. The changes are executed inline. +func (sb *backend) Prepare(chain consensus.ChainReader, header *types.Header) error { + // unused fields, force to set to empty + header.Coinbase = common.Address{} + header.Nonce = emptyNonce + header.MixDigest = types.IstanbulDigest + + // copy the parent extra data as the header extra data + number := header.Number.Uint64() + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + // use the same difficulty for all blocks + header.Difficulty = defaultDifficulty + + // Assemble the voting snapshot + snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + + // get valid candidate list + sb.candidatesLock.RLock() + var addresses []common.Address + var authorizes []bool + for address, authorize := range sb.candidates { + if snap.checkVote(address, authorize) { + addresses = append(addresses, address) + authorizes = append(authorizes, authorize) + } + } + sb.candidatesLock.RUnlock() + + // pick one of the candidates randomly + if len(addresses) > 0 { + index := rand.Intn(len(addresses)) + // add validator voting in coinbase + header.Coinbase = addresses[index] + if authorizes[index] { + copy(header.Nonce[:], nonceAuthVote) + } else { + copy(header.Nonce[:], nonceDropVote) + } + } + + // add validators in snapshot to extraData's validators section + extra, err := prepareExtra(header, snap.validators()) + if err != nil { + return err + } + header.Extra = extra + + // set header's timestamp + header.Time = parent.Time + sb.config.BlockPeriod + if header.Time < uint64(time.Now().Unix()) { + header.Time = uint64(time.Now().Unix()) + } + return nil +} + +// Finalize runs any post-transaction state modifications (e.g. block rewards) +// and assembles the final block. +// +// Note, the block header and state database might be updated to reflect any +// consensus rules that happen at finalization (e.g. block rewards). +func (sb *backend) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, + uncles []*types.Header) { + // No block rewards in Istanbul, so the state remains as is and uncles are dropped + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.UncleHash = nilUncleHash +} + +// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, +// nor block rewards given, and returns the final block. +func (sb *backend) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + /// No block rewards in Istanbul, so the state remains as is and uncles are dropped + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.UncleHash = nilUncleHash + + // Assemble and return the final block for sealing + return types.NewBlock(header, txs, nil, receipts), nil +} + +// Seal generates a new block for the given input block with the local miner's +// seal place on top. +func (sb *backend) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + + // update the block header timestamp and signature and propose the block to core engine + header := block.Header() + number := header.Number.Uint64() + // Bail out if we're unauthorized to sign a block + snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + if _, v := snap.ValSet.GetByAddress(sb.address); v == nil { + return errUnauthorized + } + + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + block, err = sb.updateBlock(parent, block) + if err != nil { + return err + } + + delay := time.Unix(int64(block.Header().Time), 0).Sub(now()) + + go func() { + // wait for the timestamp of header, use this to adjust the block period + select { + case <-time.After(delay): + case <-stop: + results <- nil + return + } + + // get the proposed block hash and clear it if the seal() is completed. + sb.sealMu.Lock() + sb.proposedBlockHash = block.Hash() + + defer func() { + sb.proposedBlockHash = common.Hash{} + sb.sealMu.Unlock() + }() + // post block into Istanbul engine + go sb.EventMux().Post(istanbul.RequestEvent{ + Proposal: block, + }) + for { + select { + case result := <-sb.commitCh: + // if the block hash and the hash from channel are the same, + // return the result. Otherwise, keep waiting the next hash. + if result != nil && block.Hash() == result.Hash() { + results <- result + return + } + case <-stop: + results <- nil + return + } + } + }() + return nil +} + +// update timestamp and signature of the block based on its number of transactions +func (sb *backend) updateBlock(parent *types.Header, block *types.Block) (*types.Block, error) { + header := block.Header() + // sign the hash + seal, err := sb.Sign(sigHash(header).Bytes()) + if err != nil { + return nil, err + } + + err = writeSeal(header, seal) + if err != nil { + return nil, err + } + + return block.WithSeal(header), nil +} + +// APIs returns the RPC APIs this consensus engine provides. +func (sb *backend) APIs(chain consensus.ChainReader) []rpc.API { + return []rpc.API{{ + Namespace: "istanbul", + Version: "1.0", + Service: &API{chain: chain, istanbul: sb}, + Public: true, + }} +} + +// Start implements consensus.Istanbul.Start +func (sb *backend) Start(chain consensus.ChainReader, currentBlock func() *types.Block, hasBadBlock func(hash common.Hash) bool) error { + sb.coreMu.Lock() + defer sb.coreMu.Unlock() + if sb.coreStarted { + return istanbul.ErrStartedEngine + } + + // clear previous data + sb.proposedBlockHash = common.Hash{} + if sb.commitCh != nil { + close(sb.commitCh) + } + sb.commitCh = make(chan *types.Block, 1) + + sb.chain = chain + sb.currentBlock = currentBlock + sb.hasBadBlock = hasBadBlock + + if err := sb.core.Start(); err != nil { + return err + } + + sb.coreStarted = true + return nil +} + +// Stop implements consensus.Istanbul.Stop +func (sb *backend) Stop() error { + sb.coreMu.Lock() + defer sb.coreMu.Unlock() + if !sb.coreStarted { + return istanbul.ErrStoppedEngine + } + if err := sb.core.Stop(); err != nil { + return err + } + sb.coreStarted = false + return nil +} + +// snapshot retrieves the authorization snapshot at a given point in time. +func (sb *backend) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { + // Search for a snapshot in memory or on disk for checkpoints + var ( + headers []*types.Header + snap *Snapshot + ) + for snap == nil { + // If an in-memory snapshot was found, use that + if s, ok := sb.recents.Get(hash); ok { + snap = s.(*Snapshot) + break + } + // If an on-disk checkpoint snapshot can be found, use that + if number%checkpointInterval == 0 { + if s, err := loadSnapshot(sb.config.Epoch, sb.db, hash); err == nil { + log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash) + snap = s + break + } + } + // If we're at block zero, make a snapshot + if number == 0 { + genesis := chain.GetHeaderByNumber(0) + if err := sb.VerifyHeader(chain, genesis, false); err != nil { + return nil, err + } + istanbulExtra, err := types.ExtractIstanbulExtra(genesis) + if err != nil { + return nil, err + } + snap = newSnapshot(sb.config.Epoch, 0, genesis.Hash(), validator.NewSet(istanbulExtra.Validators, sb.config.ProposerPolicy)) + if err := snap.store(sb.db); err != nil { + return nil, err + } + log.Trace("Stored genesis voting snapshot to disk") + break + } + // No snapshot for this header, gather the header and move backward + var header *types.Header + if len(parents) > 0 { + // If we have explicit parents, pick from there (enforced) + header = parents[len(parents)-1] + if header.Hash() != hash || header.Number.Uint64() != number { + return nil, consensus.ErrUnknownAncestor + } + parents = parents[:len(parents)-1] + } else { + // No explicit parents (or no more left), reach out to the database + header = chain.GetHeader(hash, number) + if header == nil { + return nil, consensus.ErrUnknownAncestor + } + } + headers = append(headers, header) + number, hash = number-1, header.ParentHash + } + // Previous snapshot found, apply any pending headers on top of it + for i := 0; i < len(headers)/2; i++ { + headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] + } + snap, err := snap.apply(headers) + if err != nil { + return nil, err + } + sb.recents.Add(snap.Hash, snap) + + // If we've generated a new checkpoint snapshot, save to disk + if snap.Number%checkpointInterval == 0 && len(headers) > 0 { + if err = snap.store(sb.db); err != nil { + return nil, err + } + log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash) + } + return snap, err +} + +// FIXME: Need to update this for Istanbul +// sigHash returns the hash which is used as input for the Istanbul +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// Note, the method requires the extra data to be at least 65 bytes, otherwise it +// panics. This is done to avoid accidentally using both forms (signature present +// or not), which could be abused to produce different hashes for the same header. +func sigHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewLegacyKeccak256() + + // Clean seal is required for calculating proposer seal. + rlp.Encode(hasher, types.IstanbulFilteredHeader(header, false)) + hasher.Sum(hash[:0]) + return hash +} + +// SealHash returns the hash of a block prior to it being sealed. +func (sb *backend) SealHash(header *types.Header) common.Hash { + return sigHash(header) +} + +// ecrecover extracts the Ethereum account address from a signed header. +func ecrecover(header *types.Header) (common.Address, error) { + hash := header.Hash() + if addr, ok := recentAddresses.Get(hash); ok { + return addr.(common.Address), nil + } + + // Retrieve the signature from the header extra-data + istanbulExtra, err := types.ExtractIstanbulExtra(header) + if err != nil { + return common.Address{}, err + } + + addr, err := istanbul.GetSignatureAddress(sigHash(header).Bytes(), istanbulExtra.Seal) + if err != nil { + return addr, err + } + recentAddresses.Add(hash, addr) + return addr, nil +} + +// prepareExtra returns a extra-data of the given header and validators +func prepareExtra(header *types.Header, vals []common.Address) ([]byte, error) { + var buf bytes.Buffer + + // compensate the lack bytes if header.Extra is not enough IstanbulExtraVanity bytes. + if len(header.Extra) < types.IstanbulExtraVanity { + header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity-len(header.Extra))...) + } + buf.Write(header.Extra[:types.IstanbulExtraVanity]) + + ist := &types.IstanbulExtra{ + Validators: vals, + Seal: []byte{}, + CommittedSeal: [][]byte{}, + } + + payload, err := rlp.EncodeToBytes(&ist) + if err != nil { + return nil, err + } + + return append(buf.Bytes(), payload...), nil +} + +// writeSeal writes the extra-data field of the given header with the given seals. +// suggest to rename to writeSeal. +func writeSeal(h *types.Header, seal []byte) error { + if len(seal)%types.IstanbulExtraSeal != 0 { + return errInvalidSignature + } + + istanbulExtra, err := types.ExtractIstanbulExtra(h) + if err != nil { + return err + } + + istanbulExtra.Seal = seal + payload, err := rlp.EncodeToBytes(&istanbulExtra) + if err != nil { + return err + } + + h.Extra = append(h.Extra[:types.IstanbulExtraVanity], payload...) + return nil +} + +// writeCommittedSeals writes the extra-data field of a block header with given committed seals. +func writeCommittedSeals(h *types.Header, committedSeals [][]byte) error { + if len(committedSeals) == 0 { + return errInvalidCommittedSeals + } + + for _, seal := range committedSeals { + if len(seal) != types.IstanbulExtraSeal { + return errInvalidCommittedSeals + } + } + + istanbulExtra, err := types.ExtractIstanbulExtra(h) + if err != nil { + return err + } + + istanbulExtra.CommittedSeal = make([][]byte, len(committedSeals)) + copy(istanbulExtra.CommittedSeal, committedSeals) + + payload, err := rlp.EncodeToBytes(&istanbulExtra) + if err != nil { + return err + } + + h.Extra = append(h.Extra[:types.IstanbulExtraVanity], payload...) + return nil +} diff --git a/consensus/istanbul/backend/engine_test.go b/consensus/istanbul/backend/engine_test.go new file mode 100644 index 0000000000..410cdc6d9d --- /dev/null +++ b/consensus/istanbul/backend/engine_test.go @@ -0,0 +1,579 @@ +// Copyright 2017 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 . + +package backend + +import ( + "bytes" + "crypto/ecdsa" + "math/big" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// in this test, we can set n to 1, and it means we can process Istanbul and commit a +// block by one node. Otherwise, if n is larger than 1, we have to generate +// other fake events to process Istanbul. +func newBlockChain(n int) (*core.BlockChain, *backend) { + genesis, nodeKeys := getGenesisAndKeys(n) + memDB := rawdb.NewMemoryDatabase() + config := istanbul.DefaultConfig + // Use the first key as private key + b, _ := New(config, nodeKeys[0], memDB).(*backend) + genesis.MustCommit(memDB) + blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, b, vm.Config{}, nil, nil) + if err != nil { + panic(err) + } + b.Start(blockchain, blockchain.CurrentBlock, blockchain.HasBadBlock) + snap, err := b.snapshot(blockchain, 0, common.Hash{}, nil) + if err != nil { + panic(err) + } + if snap == nil { + panic("failed to get snapshot") + } + proposerAddr := snap.ValSet.GetProposer().Address() + + // find proposer key + for _, key := range nodeKeys { + addr := crypto.PubkeyToAddress(key.PublicKey) + if addr.String() == proposerAddr.String() { + b.privateKey = key + b.address = addr + } + } + + return blockchain, b +} + +func getGenesisAndKeys(n int) (*core.Genesis, []*ecdsa.PrivateKey) { + // Setup validators + var nodeKeys = make([]*ecdsa.PrivateKey, n) + var addrs = make([]common.Address, n) + for i := 0; i < n; i++ { + nodeKeys[i], _ = crypto.GenerateKey() + addrs[i] = crypto.PubkeyToAddress(nodeKeys[i].PublicKey) + } + + // generate genesis block + genesis := core.DefaultGenesisBlock() + genesis.Config = params.TestChainConfig + // force enable Istanbul engine + genesis.Config.Istanbul = ¶ms.IstanbulConfig{} + genesis.Config.Ethash = nil + genesis.Difficulty = defaultDifficulty + genesis.Nonce = emptyNonce.Uint64() + genesis.Mixhash = types.IstanbulDigest + + appendValidators(genesis, addrs) + return genesis, nodeKeys +} + +func appendValidators(genesis *core.Genesis, addrs []common.Address) { + + if len(genesis.ExtraData) < types.IstanbulExtraVanity { + genesis.ExtraData = append(genesis.ExtraData, bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity)...) + } + genesis.ExtraData = genesis.ExtraData[:types.IstanbulExtraVanity] + + ist := &types.IstanbulExtra{ + Validators: addrs, + Seal: []byte{}, + CommittedSeal: [][]byte{}, + } + + istPayload, err := rlp.EncodeToBytes(&ist) + if err != nil { + panic("failed to encode istanbul extra") + } + genesis.ExtraData = append(genesis.ExtraData, istPayload...) +} + +func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header { + header := &types.Header{ + ParentHash: parent.Hash(), + Number: parent.Number().Add(parent.Number(), common.Big1), + GasLimit: core.CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + GasUsed: 0, + Extra: parent.Extra(), + Time: parent.Time() + config.BlockPeriod, + + Difficulty: defaultDifficulty, + } + return header +} + +func makeBlock(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block { + block := makeBlockWithoutSeal(chain, engine, parent) + stopCh := make(chan struct{}) + resultCh := make(chan *types.Block, 10) + go engine.Seal(chain, block, resultCh, stopCh) + blk := <-resultCh + return blk +} + +func makeBlockWithoutSeal(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block { + header := makeHeader(parent, engine.config) + engine.Prepare(chain, header) + state, _, _ := chain.StateAt(parent.Root()) + block, _ := engine.FinalizeAndAssemble(chain, header, state, nil, nil, nil) + return block +} + +func TestPrepare(t *testing.T) { + chain, engine := newBlockChain(1) + header := makeHeader(chain.Genesis(), engine.config) + err := engine.Prepare(chain, header) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + header.ParentHash = common.StringToHash("1234567890") + err = engine.Prepare(chain, header) + if err != consensus.ErrUnknownAncestor { + t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor) + } +} + +func TestSealStopChannel(t *testing.T) { + chain, engine := newBlockChain(4) + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + stop := make(chan struct{}, 1) + eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{}) + eventLoop := func() { + ev := <-eventSub.Chan() + _, ok := ev.Data.(istanbul.RequestEvent) + if !ok { + t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) + } + stop <- struct{}{} + eventSub.Unsubscribe() + } + go eventLoop() + resultCh := make(chan *types.Block, 10) + go func() { + err := engine.Seal(chain, block, resultCh, stop) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + }() + + finalBlock := <-resultCh + if finalBlock != nil { + t.Errorf("block mismatch: have %v, want nil", finalBlock) + } +} + +func TestSealCommittedOtherHash(t *testing.T) { + chain, engine := newBlockChain(4) + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + otherBlock := makeBlockWithoutSeal(chain, engine, block) + expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) + + eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{}) + blockOutputChannel := make(chan *types.Block) + stopChannel := make(chan struct{}) + + go func() { + ev := <-eventSub.Chan() + if _, ok := ev.Data.(istanbul.RequestEvent); !ok { + t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) + } + if err := engine.Commit(otherBlock, [][]byte{expectedCommittedSeal}); err != nil { + t.Error(err.Error()) + } + eventSub.Unsubscribe() + }() + + go func() { + if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil { + t.Error(err.Error()) + } + }() + + select { + case <-blockOutputChannel: + t.Error("Wrong block found!") + default: + //no block found, stop the sealing + close(stopChannel) + } + + output := <-blockOutputChannel + if output != nil { + t.Error("Block not nil!") + } +} + +func TestSealCommitted(t *testing.T) { + chain, engine := newBlockChain(1) + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block) + resultCh := make(chan *types.Block, 10) + go func() { + err := engine.Seal(chain, block, resultCh, make(chan struct{})) + + if err != nil { + t.Errorf("error mismatch: have %v, want %v", err, expectedBlock) + } + }() + + finalBlock := <-resultCh + if finalBlock.Hash() != expectedBlock.Hash() { + t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash()) + } +} + +func TestVerifyHeader(t *testing.T) { + chain, engine := newBlockChain(1) + + // errEmptyCommittedSeals case + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + block, _ = engine.updateBlock(chain.Genesis().Header(), block) + err := engine.VerifyHeader(chain, block.Header(), false) + if err != errEmptyCommittedSeals { + t.Errorf("error mismatch: have %v, want %v", err, errEmptyCommittedSeals) + } + + // short extra data + header := block.Header() + header.Extra = []byte{} + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidExtraDataFormat { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat) + } + // incorrect extra format + header.Extra = []byte("0000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000000") + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidExtraDataFormat { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat) + } + + // non zero MixDigest + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + header.MixDigest = common.StringToHash("123456789") + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidMixDigest { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidMixDigest) + } + + // invalid uncles hash + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + header.UncleHash = common.StringToHash("123456789") + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidUncleHash { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidUncleHash) + } + + // invalid difficulty + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + header.Difficulty = big.NewInt(2) + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidDifficulty { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidDifficulty) + } + + // invalid timestamp + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + header.Time = chain.Genesis().Time() + (engine.config.BlockPeriod - 1) + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidTimestamp { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidTimestamp) + } + + // future block + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + header.Time = uint64(now().Unix() + 10) + err = engine.VerifyHeader(chain, header, false) + if err != consensus.ErrFutureBlock { + t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrFutureBlock) + } + + // future block which is within AllowedFutureBlockTime + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + header.Time = new(big.Int).Add(big.NewInt(now().Unix()), new(big.Int).SetUint64(10)).Uint64() + priorValue := engine.config.AllowedFutureBlockTime + engine.config.AllowedFutureBlockTime = 10 + err = engine.VerifyHeader(chain, header, false) + engine.config.AllowedFutureBlockTime = priorValue //restore changed value + if err == consensus.ErrFutureBlock { + t.Errorf("error mismatch: have %v, want nil", err) + } + + // invalid nonce + block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) + header = block.Header() + copy(header.Nonce[:], hexutil.MustDecode("0x111111111111")) + header.Number = big.NewInt(int64(engine.config.Epoch)) + err = engine.VerifyHeader(chain, header, false) + if err != errInvalidNonce { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidNonce) + } +} + +func TestVerifySeal(t *testing.T) { + chain, engine := newBlockChain(1) + genesis := chain.Genesis() + // cannot verify genesis + err := engine.VerifySeal(chain, genesis.Header()) + if err != errUnknownBlock { + t.Errorf("error mismatch: have %v, want %v", err, errUnknownBlock) + } + + block := makeBlock(chain, engine, genesis) + // change block content + header := block.Header() + header.Number = big.NewInt(4) + block1 := block.WithSeal(header) + err = engine.VerifySeal(chain, block1.Header()) + if err != errUnauthorized { + t.Errorf("error mismatch: have %v, want %v", err, errUnauthorized) + } + + // unauthorized users but still can get correct signer address + engine.privateKey, _ = crypto.GenerateKey() + err = engine.VerifySeal(chain, block.Header()) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } +} + +func TestVerifyHeaders(t *testing.T) { + chain, engine := newBlockChain(1) + genesis := chain.Genesis() + + // success case + headers := []*types.Header{} + blocks := []*types.Block{} + size := 100 + + for i := 0; i < size; i++ { + var b *types.Block + if i == 0 { + b = makeBlockWithoutSeal(chain, engine, genesis) + b, _ = engine.updateBlock(genesis.Header(), b) + } else { + b = makeBlockWithoutSeal(chain, engine, blocks[i-1]) + b, _ = engine.updateBlock(blocks[i-1].Header(), b) + } + blocks = append(blocks, b) + headers = append(headers, blocks[i].Header()) + } + now = func() time.Time { + return time.Unix(int64(headers[size-1].Time), 0) + } + _, results := engine.VerifyHeaders(chain, headers, nil) + const timeoutDura = 2 * time.Second + timeout := time.NewTimer(timeoutDura) + index := 0 +OUT1: + for { + select { + case err := <-results: + if err != nil { + if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { + t.Errorf("error mismatch: have %v, want errEmptyCommittedSeals|errInvalidCommittedSeals|ErrUnknownAncestor", err) + break OUT1 + } + } + index++ + if index == size { + break OUT1 + } + case <-timeout.C: + break OUT1 + } + } + _, results = engine.VerifyHeaders(chain, headers, nil) + timeout = time.NewTimer(timeoutDura) +OUT2: + for { + select { + case err := <-results: + if err != nil { + if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { + t.Errorf("error mismatch: have %v, want errEmptyCommittedSeals|errInvalidCommittedSeals|ErrUnknownAncestor", err) + break OUT2 + } + } + case <-timeout.C: + break OUT2 + } + } + // error header cases + headers[2].Number = big.NewInt(100) + _, results = engine.VerifyHeaders(chain, headers, nil) + timeout = time.NewTimer(timeoutDura) + index = 0 + errors := 0 + expectedErrors := 0 +OUT3: + for { + select { + case err := <-results: + if err != nil { + if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { + errors++ + } + } + index++ + if index == size { + if errors != expectedErrors { + t.Errorf("error mismatch: have %v, want %v", errors, expectedErrors) + } + break OUT3 + } + case <-timeout.C: + break OUT3 + } + } +} + +func TestPrepareExtra(t *testing.T) { + validators := make([]common.Address, 4) + validators[0] = common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")) + validators[1] = common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")) + validators[2] = common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")) + validators[3] = common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")) + + vanity := make([]byte, types.IstanbulExtraVanity) + expectedResult := append(vanity, hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0")...) + + h := &types.Header{ + Extra: vanity, + } + + payload, err := prepareExtra(h, validators) + if err != nil { + t.Errorf("error mismatch: have %v, want: nil", err) + } + if !reflect.DeepEqual(payload, expectedResult) { + t.Errorf("payload mismatch: have %v, want %v", payload, expectedResult) + } + + // append useless information to extra-data + h.Extra = append(vanity, make([]byte, 15)...) + + payload, _ = prepareExtra(h, validators) + if !reflect.DeepEqual(payload, expectedResult) { + t.Errorf("payload mismatch: have %v, want %v", payload, expectedResult) + } +} + +func TestWriteSeal(t *testing.T) { + vanity := bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity) + istRawData := hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0") + expectedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) + expectedIstExtra := &types.IstanbulExtra{ + Validators: []common.Address{ + common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")), + common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")), + common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")), + common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")), + }, + Seal: expectedSeal, + CommittedSeal: [][]byte{}, + } + var expectedErr error + + h := &types.Header{ + Extra: append(vanity, istRawData...), + } + + // normal case + err := writeSeal(h, expectedSeal) + if err != expectedErr { + t.Errorf("error mismatch: have %v, want %v", err, expectedErr) + } + + // verify istanbul extra-data + istExtra, err := types.ExtractIstanbulExtra(h) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + if !reflect.DeepEqual(istExtra, expectedIstExtra) { + t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra) + } + + // invalid seal + unexpectedSeal := append(expectedSeal, make([]byte, 1)...) + err = writeSeal(h, unexpectedSeal) + if err != errInvalidSignature { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature) + } +} + +func TestWriteCommittedSeals(t *testing.T) { + vanity := bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity) + istRawData := hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0") + expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) + expectedIstExtra := &types.IstanbulExtra{ + Validators: []common.Address{ + common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")), + common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")), + common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")), + common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")), + }, + Seal: []byte{}, + CommittedSeal: [][]byte{expectedCommittedSeal}, + } + var expectedErr error + + h := &types.Header{ + Extra: append(vanity, istRawData...), + } + + // normal case + err := writeCommittedSeals(h, [][]byte{expectedCommittedSeal}) + if err != expectedErr { + t.Errorf("error mismatch: have %v, want %v", err, expectedErr) + } + + // verify istanbul extra-data + istExtra, err := types.ExtractIstanbulExtra(h) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + if !reflect.DeepEqual(istExtra, expectedIstExtra) { + t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra) + } + + // invalid seal + unexpectedCommittedSeal := append(expectedCommittedSeal, make([]byte, 1)...) + err = writeCommittedSeals(h, [][]byte{unexpectedCommittedSeal}) + if err != errInvalidCommittedSeals { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidCommittedSeals) + } +} diff --git a/consensus/istanbul/backend/handler.go b/consensus/istanbul/backend/handler.go new file mode 100644 index 0000000000..8592092c9e --- /dev/null +++ b/consensus/istanbul/backend/handler.go @@ -0,0 +1,138 @@ +// Copyright 2017 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 . + +package backend + +import ( + "bytes" + "errors" + "io/ioutil" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + lru "github.com/hashicorp/golang-lru" +) + +const ( + istanbulMsg = 0x11 + NewBlockMsg = 0x07 +) + +var ( + // errDecodeFailed is returned when decode message fails + errDecodeFailed = errors.New("fail to decode istanbul message") +) + +// Protocol implements consensus.Engine.Protocol +func (sb *backend) Protocol() consensus.Protocol { + return consensus.IstanbulProtocol +} + +func (sb *backend) decode(msg p2p.Msg) ([]byte, common.Hash, error) { + var data []byte + if err := msg.Decode(&data); err != nil { + return nil, common.Hash{}, errDecodeFailed + } + + return data, istanbul.RLPHash(data), nil +} + +// HandleMsg implements consensus.Handler.HandleMsg +func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) { + sb.coreMu.Lock() + defer sb.coreMu.Unlock() + if msg.Code == istanbulMsg { + if !sb.coreStarted { + return true, istanbul.ErrStoppedEngine + } + + data, hash, err := sb.decode(msg) + if err != nil { + return true, errDecodeFailed + } + // Mark peer's message + ms, ok := sb.recentMessages.Get(addr) + var m *lru.ARCCache + if ok { + m, _ = ms.(*lru.ARCCache) + } else { + m, _ = lru.NewARC(inmemoryMessages) + sb.recentMessages.Add(addr, m) + } + m.Add(hash, true) + + // Mark self known message + if _, ok := sb.knownMessages.Get(hash); ok { + return true, nil + } + sb.knownMessages.Add(hash, true) + + go sb.istanbulEventMux.Post(istanbul.MessageEvent{ + Payload: data, + }) + return true, nil + } + //https://github.com/ConsenSys/quorum/pull/539 + //https://github.com/ConsenSys/quorum/issues/389 + if msg.Code == NewBlockMsg && sb.core.IsProposer() { // eth.NewBlockMsg: import cycle + // this case is to safeguard the race of similar block which gets propagated from other node while this node is proposing + // as p2p.Msg can only be decoded once (get EOF for any subsequence read), we need to make sure the payload is restored after we decode it + log.Debug("Proposer received NewBlockMsg", "size", msg.Size, "payload.type", reflect.TypeOf(msg.Payload), "sender", addr) + if reader, ok := msg.Payload.(*bytes.Reader); ok { + payload, err := ioutil.ReadAll(reader) + if err != nil { + return true, err + } + reader.Reset(payload) // ready to be decoded + defer reader.Reset(payload) // restore so main eth/handler can decode + var request struct { // this has to be same as eth/protocol.go#newBlockData as we are reading NewBlockMsg + Block *types.Block + TD *big.Int + } + if err := msg.Decode(&request); err != nil { + log.Debug("Proposer was unable to decode the NewBlockMsg", "error", err) + return false, nil + } + newRequestedBlock := request.Block + if newRequestedBlock.Header().MixDigest == types.IstanbulDigest && sb.core.IsCurrentProposal(newRequestedBlock.Hash()) { + log.Debug("Proposer already proposed this block", "hash", newRequestedBlock.Hash(), "sender", addr) + return true, nil + } + } + } + return false, nil +} + +// SetBroadcaster implements consensus.Handler.SetBroadcaster +func (sb *backend) SetBroadcaster(broadcaster consensus.Broadcaster) { + sb.broadcaster = broadcaster +} + +func (sb *backend) NewChainHead() error { + sb.coreMu.RLock() + defer sb.coreMu.RUnlock() + if !sb.coreStarted { + return istanbul.ErrStoppedEngine + } + go sb.istanbulEventMux.Post(istanbul.FinalCommittedEvent{}) + return nil +} diff --git a/consensus/istanbul/backend/handler_test.go b/consensus/istanbul/backend/handler_test.go new file mode 100644 index 0000000000..ea347c6071 --- /dev/null +++ b/consensus/istanbul/backend/handler_test.go @@ -0,0 +1,181 @@ +// Copyright 2015 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 . + +package backend + +import ( + "bytes" + "io/ioutil" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" + lru "github.com/hashicorp/golang-lru" +) + +func TestIstanbulMessage(t *testing.T) { + _, backend := newBlockChain(1) + + // generate one msg + data := []byte("data1") + hash := istanbul.RLPHash(data) + msg := makeMsg(istanbulMsg, data) + addr := common.StringToAddress("address") + + // 1. this message should not be in cache + // for peers + if _, ok := backend.recentMessages.Get(addr); ok { + t.Fatalf("the cache of messages for this peer should be nil") + } + + // for self + if _, ok := backend.knownMessages.Get(hash); ok { + t.Fatalf("the cache of messages should be nil") + } + + // 2. this message should be in cache after we handle it + _, err := backend.HandleMsg(addr, msg) + if err != nil { + t.Fatalf("handle message failed: %v", err) + } + // for peers + if ms, ok := backend.recentMessages.Get(addr); ms == nil || !ok { + t.Fatalf("the cache of messages for this peer cannot be nil") + } else if m, ok := ms.(*lru.ARCCache); !ok { + t.Fatalf("the cache of messages for this peer cannot be casted") + } else if _, ok := m.Get(hash); !ok { + t.Fatalf("the cache of messages for this peer cannot be found") + } + + // for self + if _, ok := backend.knownMessages.Get(hash); !ok { + t.Fatalf("the cache of messages cannot be found") + } +} + +func makeMsg(msgcode uint64, data interface{}) p2p.Msg { + size, r, _ := rlp.EncodeToReader(data) + return p2p.Msg{Code: msgcode, Size: uint32(size), Payload: r} +} + +func TestHandleNewBlockMessage_whenTypical(t *testing.T) { + _, backend := newBlockChain(1) + arbitraryAddress := common.StringToAddress("arbitrary") + arbitraryBlock, arbitraryP2PMessage := buildArbitraryP2PNewBlockMessage(t, false) + postAndWait(backend, arbitraryBlock, t) + + handled, err := backend.HandleMsg(arbitraryAddress, arbitraryP2PMessage) + + if err != nil { + t.Errorf("expected message being handled successfully but got %s", err) + } + if !handled { + t.Errorf("expected message being handled but not") + } + if _, err := ioutil.ReadAll(arbitraryP2PMessage.Payload); err != nil { + t.Errorf("expected p2p message payload is restored") + } +} + +func TestHandleNewBlockMessage_whenNotAProposedBlock(t *testing.T) { + _, backend := newBlockChain(1) + arbitraryAddress := common.StringToAddress("arbitrary") + _, arbitraryP2PMessage := buildArbitraryP2PNewBlockMessage(t, false) + postAndWait(backend, types.NewBlock(&types.Header{ + Number: big.NewInt(1), + Root: common.StringToHash("someroot"), + GasLimit: 1, + MixDigest: types.IstanbulDigest, + }, nil, nil, nil), t) + + handled, err := backend.HandleMsg(arbitraryAddress, arbitraryP2PMessage) + + if err != nil { + t.Errorf("expected message being handled successfully but got %s", err) + } + if handled { + t.Errorf("expected message not being handled") + } + if _, err := ioutil.ReadAll(arbitraryP2PMessage.Payload); err != nil { + t.Errorf("expected p2p message payload is restored") + } +} + +func TestHandleNewBlockMessage_whenFailToDecode(t *testing.T) { + _, backend := newBlockChain(1) + arbitraryAddress := common.StringToAddress("arbitrary") + _, arbitraryP2PMessage := buildArbitraryP2PNewBlockMessage(t, true) + postAndWait(backend, types.NewBlock(&types.Header{ + Number: big.NewInt(1), + GasLimit: 1, + MixDigest: types.IstanbulDigest, + }, nil, nil, nil), t) + + handled, err := backend.HandleMsg(arbitraryAddress, arbitraryP2PMessage) + + if err != nil { + t.Errorf("expected message being handled successfully but got %s", err) + } + if handled { + t.Errorf("expected message not being handled") + } + if _, err := ioutil.ReadAll(arbitraryP2PMessage.Payload); err != nil { + t.Errorf("expected p2p message payload is restored") + } +} + +func postAndWait(backend *backend, block *types.Block, t *testing.T) { + eventSub := backend.EventMux().Subscribe(istanbul.RequestEvent{}) + defer eventSub.Unsubscribe() + stop := make(chan struct{}, 1) + eventLoop := func() { + <-eventSub.Chan() + stop <- struct{}{} + } + go eventLoop() + if err := backend.EventMux().Post(istanbul.RequestEvent{ + Proposal: block, + }); err != nil { + t.Fatalf("%s", err) + } + <-stop +} + +func buildArbitraryP2PNewBlockMessage(t *testing.T, invalidMsg bool) (*types.Block, p2p.Msg) { + arbitraryBlock := types.NewBlock(&types.Header{ + Number: big.NewInt(1), + GasLimit: 0, + MixDigest: types.IstanbulDigest, + }, nil, nil, nil) + request := []interface{}{&arbitraryBlock, big.NewInt(1)} + if invalidMsg { + request = []interface{}{"invalid msg"} + } + size, r, err := rlp.EncodeToReader(request) + if err != nil { + t.Fatalf("can't encode due to %s", err) + } + payload, err := ioutil.ReadAll(r) + if err != nil { + t.Fatalf("can't read payload due to %s", err) + } + arbitraryP2PMessage := p2p.Msg{Code: 0x07, Size: uint32(size), Payload: bytes.NewReader(payload)} + return arbitraryBlock, arbitraryP2PMessage +} diff --git a/consensus/istanbul/backend/snapshot.go b/consensus/istanbul/backend/snapshot.go new file mode 100644 index 0000000000..e369147f81 --- /dev/null +++ b/consensus/istanbul/backend/snapshot.go @@ -0,0 +1,321 @@ +// Copyright 2017 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 . + +package backend + +import ( + "bytes" + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" +) + +const ( + dbKeySnapshotPrefix = "istanbul-snapshot" +) + +// Vote represents a single vote that an authorized validator made to modify the +// list of authorizations. +type Vote struct { + Validator common.Address `json:"validator"` // Authorized validator that cast this vote + Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes) + Address common.Address `json:"address"` // Account being voted on to change its authorization + Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account +} + +// Tally is a simple vote tally to keep the current score of votes. Votes that +// go against the proposal aren't counted since it's equivalent to not voting. +type Tally struct { + Authorize bool `json:"authorize"` // Whether the vote it about authorizing or kicking someone + Votes int `json:"votes"` // Number of votes until now wanting to pass the proposal +} + +// Snapshot is the state of the authorization voting at a given point in time. +type Snapshot struct { + Epoch uint64 // The number of blocks after which to checkpoint and reset the pending votes + + Number uint64 // Block number where the snapshot was created + Hash common.Hash // Block hash where the snapshot was created + Votes []*Vote // List of votes cast in chronological order + Tally map[common.Address]Tally // Current vote tally to avoid recalculating + ValSet istanbul.ValidatorSet // Set of authorized validators at this moment +} + +// newSnapshot create a new snapshot with the specified startup parameters. This +// method does not initialize the set of recent validators, so only ever use if for +// the genesis block. +func newSnapshot(epoch uint64, number uint64, hash common.Hash, valSet istanbul.ValidatorSet) *Snapshot { + snap := &Snapshot{ + Epoch: epoch, + Number: number, + Hash: hash, + ValSet: valSet, + Tally: make(map[common.Address]Tally), + } + return snap +} + +// loadSnapshot loads an existing snapshot from the database. +func loadSnapshot(epoch uint64, db ethdb.Database, hash common.Hash) (*Snapshot, error) { + blob, err := db.Get(append([]byte(dbKeySnapshotPrefix), hash[:]...)) + if err != nil { + return nil, err + } + snap := new(Snapshot) + if err := json.Unmarshal(blob, snap); err != nil { + return nil, err + } + snap.Epoch = epoch + + return snap, nil +} + +// store inserts the snapshot into the database. +func (s *Snapshot) store(db ethdb.Database) error { + blob, err := json.Marshal(s) + if err != nil { + return err + } + return db.Put(append([]byte(dbKeySnapshotPrefix), s.Hash[:]...), blob) +} + +// copy creates a deep copy of the snapshot, though not the individual votes. +func (s *Snapshot) copy() *Snapshot { + cpy := &Snapshot{ + Epoch: s.Epoch, + Number: s.Number, + Hash: s.Hash, + ValSet: s.ValSet.Copy(), + Votes: make([]*Vote, len(s.Votes)), + Tally: make(map[common.Address]Tally), + } + + for address, tally := range s.Tally { + cpy.Tally[address] = tally + } + copy(cpy.Votes, s.Votes) + + return cpy +} + +// checkVote return whether it's a valid vote +func (s *Snapshot) checkVote(address common.Address, authorize bool) bool { + _, validator := s.ValSet.GetByAddress(address) + return (validator != nil && !authorize) || (validator == nil && authorize) +} + +// cast adds a new vote into the tally. +func (s *Snapshot) cast(address common.Address, authorize bool) bool { + // Ensure the vote is meaningful + if !s.checkVote(address, authorize) { + return false + } + // Cast the vote into an existing or new tally + if old, ok := s.Tally[address]; ok { + old.Votes++ + s.Tally[address] = old + } else { + s.Tally[address] = Tally{Authorize: authorize, Votes: 1} + } + return true +} + +// uncast removes a previously cast vote from the tally. +func (s *Snapshot) uncast(address common.Address, authorize bool) bool { + // If there's no tally, it's a dangling vote, just drop + tally, ok := s.Tally[address] + if !ok { + return false + } + // Ensure we only revert counted votes + if tally.Authorize != authorize { + return false + } + // Otherwise revert the vote + if tally.Votes > 1 { + tally.Votes-- + s.Tally[address] = tally + } else { + delete(s.Tally, address) + } + return true +} + +// apply creates a new authorization snapshot by applying the given headers to +// the original one. +func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { + // Allow passing in no headers for cleaner code + if len(headers) == 0 { + return s, nil + } + // Sanity check that the headers can be applied + for i := 0; i < len(headers)-1; i++ { + if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 { + return nil, errInvalidVotingChain + } + } + if headers[0].Number.Uint64() != s.Number+1 { + return nil, errInvalidVotingChain + } + // Iterate through the headers and create a new snapshot + snap := s.copy() + + for _, header := range headers { + // Remove any votes on checkpoint blocks + number := header.Number.Uint64() + if number%s.Epoch == 0 { + snap.Votes = nil + snap.Tally = make(map[common.Address]Tally) + } + // Resolve the authorization key and check against validators + validator, err := ecrecover(header) + if err != nil { + return nil, err + } + if _, v := snap.ValSet.GetByAddress(validator); v == nil { + return nil, errUnauthorized + } + + // Header authorized, discard any previous votes from the validator + for i, vote := range snap.Votes { + if vote.Validator == validator && vote.Address == header.Coinbase { + // Uncast the vote from the cached tally + snap.uncast(vote.Address, vote.Authorize) + + // Uncast the vote from the chronological list + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + break // only one vote allowed + } + } + // Tally up the new vote from the validator + var authorize bool + switch { + case bytes.Equal(header.Nonce[:], nonceAuthVote): + authorize = true + case bytes.Equal(header.Nonce[:], nonceDropVote): + authorize = false + default: + return nil, errInvalidVote + } + if snap.cast(header.Coinbase, authorize) { + snap.Votes = append(snap.Votes, &Vote{ + Validator: validator, + Block: number, + Address: header.Coinbase, + Authorize: authorize, + }) + } + // If the vote passed, update the list of validators + if tally := snap.Tally[header.Coinbase]; tally.Votes > snap.ValSet.Size()/2 { + if tally.Authorize { + snap.ValSet.AddValidator(header.Coinbase) + } else { + snap.ValSet.RemoveValidator(header.Coinbase) + + // Discard any previous votes the deauthorized validator cast + for i := 0; i < len(snap.Votes); i++ { + if snap.Votes[i].Validator == header.Coinbase { + // Uncast the vote from the cached tally + snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize) + + // Uncast the vote from the chronological list + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + + i-- + } + } + } + // Discard any previous votes around the just changed account + for i := 0; i < len(snap.Votes); i++ { + if snap.Votes[i].Address == header.Coinbase { + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + i-- + } + } + delete(snap.Tally, header.Coinbase) + } + } + snap.Number += uint64(len(headers)) + snap.Hash = headers[len(headers)-1].Hash() + + return snap, nil +} + +// validators retrieves the list of authorized validators in ascending order. +func (s *Snapshot) validators() []common.Address { + validators := make([]common.Address, 0, s.ValSet.Size()) + for _, validator := range s.ValSet.List() { + validators = append(validators, validator.Address()) + } + for i := 0; i < len(validators); i++ { + for j := i + 1; j < len(validators); j++ { + if bytes.Compare(validators[i][:], validators[j][:]) > 0 { + validators[i], validators[j] = validators[j], validators[i] + } + } + } + return validators +} + +type snapshotJSON struct { + Epoch uint64 `json:"epoch"` + Number uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Votes []*Vote `json:"votes"` + Tally map[common.Address]Tally `json:"tally"` + + // for validator set + Validators []common.Address `json:"validators"` + Policy istanbul.ProposerPolicy `json:"policy"` +} + +func (s *Snapshot) toJSONStruct() *snapshotJSON { + return &snapshotJSON{ + Epoch: s.Epoch, + Number: s.Number, + Hash: s.Hash, + Votes: s.Votes, + Tally: s.Tally, + Validators: s.validators(), + Policy: s.ValSet.Policy(), + } +} + +// Unmarshal from a json byte array +func (s *Snapshot) UnmarshalJSON(b []byte) error { + var j snapshotJSON + if err := json.Unmarshal(b, &j); err != nil { + return err + } + + s.Epoch = j.Epoch + s.Number = j.Number + s.Hash = j.Hash + s.Votes = j.Votes + s.Tally = j.Tally + s.ValSet = validator.NewSet(j.Validators, j.Policy) + return nil +} + +// Marshal to a json byte array +func (s *Snapshot) MarshalJSON() ([]byte, error) { + j := s.toJSONStruct() + return json.Marshal(j) +} diff --git a/consensus/istanbul/backend/snapshot_test.go b/consensus/istanbul/backend/snapshot_test.go new file mode 100644 index 0000000000..fe1fade8f4 --- /dev/null +++ b/consensus/istanbul/backend/snapshot_test.go @@ -0,0 +1,455 @@ +// Copyright 2017 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 . + +package backend + +import ( + "bytes" + "crypto/ecdsa" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" +) + +type testerVote struct { + validator string + voted string + auth bool +} + +// testerAccountPool is a pool to maintain currently active tester accounts, +// mapped from textual names used in the tests below to actual Ethereum private +// keys capable of signing transactions. +type testerAccountPool struct { + accounts map[string]*ecdsa.PrivateKey +} + +func newTesterAccountPool() *testerAccountPool { + return &testerAccountPool{ + accounts: make(map[string]*ecdsa.PrivateKey), + } +} + +func (ap *testerAccountPool) sign(header *types.Header, validator string) { + // Ensure we have a persistent key for the validator + if ap.accounts[validator] == nil { + ap.accounts[validator], _ = crypto.GenerateKey() + } + // Sign the header and embed the signature in extra data + hashData := crypto.Keccak256(sigHash(header).Bytes()) + sig, _ := crypto.Sign(hashData, ap.accounts[validator]) + + writeSeal(header, sig) +} + +func (ap *testerAccountPool) address(account string) common.Address { + // Ensure we have a persistent key for the account + if ap.accounts[account] == nil { + ap.accounts[account], _ = crypto.GenerateKey() + } + // Resolve and return the Ethereum address + return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) +} + +// Tests that voting is evaluated correctly for various simple and complex scenarios. +func TestVoting(t *testing.T) { + // Define the various voting scenarios to test + tests := []struct { + epoch uint64 + validators []string + votes []testerVote + results []string + }{ + { + // Single validator, no votes cast + validators: []string{"A"}, + votes: []testerVote{{validator: "A"}}, + results: []string{"A"}, + }, { + // Single validator, voting to add two others (only accept first, second needs 2 votes) + validators: []string{"A"}, + votes: []testerVote{ + {validator: "A", voted: "B", auth: true}, + {validator: "B"}, + {validator: "A", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Two validators, voting to add three others (only accept first two, third needs 3 votes already) + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: true}, + {validator: "B", voted: "C", auth: true}, + {validator: "A", voted: "D", auth: true}, + {validator: "B", voted: "D", auth: true}, + {validator: "C"}, + {validator: "A", voted: "E", auth: true}, + {validator: "B", voted: "E", auth: true}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Single validator, dropping itself (weird, but one less cornercase by explicitly allowing this) + validators: []string{"A"}, + votes: []testerVote{ + {validator: "A", voted: "A", auth: false}, + }, + results: []string{}, + }, { + // Two validators, actually needing mutual consent to drop either of them (not fulfilled) + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Two validators, actually needing mutual consent to drop either of them (fulfilled) + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "B", auth: false}, + {validator: "B", voted: "B", auth: false}, + }, + results: []string{"A"}, + }, { + // Three validators, two of them deciding to drop the third + validators: []string{"A", "B", "C"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: false}, + {validator: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Four validators, consensus of two not being enough to drop anyone + validators: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: false}, + {validator: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Four validators, consensus of three already being enough to drop someone + validators: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {validator: "A", voted: "D", auth: false}, + {validator: "B", voted: "D", auth: false}, + {validator: "C", voted: "D", auth: false}, + }, + results: []string{"A", "B", "C"}, + }, { + // Authorizations are counted once per validator per target + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: true}, + {validator: "B"}, + {validator: "A", voted: "C", auth: true}, + {validator: "B"}, + {validator: "A", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Authorizing multiple accounts concurrently is permitted + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: true}, + {validator: "B"}, + {validator: "A", voted: "D", auth: true}, + {validator: "B"}, + {validator: "A"}, + {validator: "B", voted: "D", auth: true}, + {validator: "A"}, + {validator: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Deauthorizations are counted once per validator per target + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "B", auth: false}, + {validator: "B"}, + {validator: "A", voted: "B", auth: false}, + {validator: "B"}, + {validator: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Deauthorizing multiple accounts concurrently is permitted + validators: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: false}, + {validator: "B"}, + {validator: "C"}, + {validator: "A", voted: "D", auth: false}, + {validator: "B"}, + {validator: "C"}, + {validator: "A"}, + {validator: "B", voted: "D", auth: false}, + {validator: "C", voted: "D", auth: false}, + {validator: "A"}, + {validator: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Votes from deauthorized validators are discarded immediately (deauth votes) + validators: []string{"A", "B", "C"}, + votes: []testerVote{ + {validator: "C", voted: "B", auth: false}, + {validator: "A", voted: "C", auth: false}, + {validator: "B", voted: "C", auth: false}, + {validator: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Votes from deauthorized validators are discarded immediately (auth votes) + validators: []string{"A", "B", "C"}, + votes: []testerVote{ + {validator: "C", voted: "B", auth: false}, + {validator: "A", voted: "C", auth: false}, + {validator: "B", voted: "C", auth: false}, + {validator: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Cascading changes are not allowed, only the the account being voted on may change + validators: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: false}, + {validator: "B"}, + {validator: "C"}, + {validator: "A", voted: "D", auth: false}, + {validator: "B", voted: "C", auth: false}, + {validator: "C"}, + {validator: "A"}, + {validator: "B", voted: "D", auth: false}, + {validator: "C", voted: "D", auth: false}, + }, + results: []string{"A", "B", "C"}, + }, { + // Changes reaching consensus out of bounds (via a deauth) execute on touch + validators: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: false}, + {validator: "B"}, + {validator: "C"}, + {validator: "A", voted: "D", auth: false}, + {validator: "B", voted: "C", auth: false}, + {validator: "C"}, + {validator: "A"}, + {validator: "B", voted: "D", auth: false}, + {validator: "C", voted: "D", auth: false}, + {validator: "A"}, + {validator: "C", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch + validators: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: false}, + {validator: "B"}, + {validator: "C"}, + {validator: "A", voted: "D", auth: false}, + {validator: "B", voted: "C", auth: false}, + {validator: "C"}, + {validator: "A"}, + {validator: "B", voted: "D", auth: false}, + {validator: "C", voted: "D", auth: false}, + {validator: "A"}, + {validator: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B", "C"}, + }, { + // Ensure that pending votes don't survive authorization status changes. This + // corner case can only appear if a validator is quickly added, remove and then + // readded (or the inverse), while one of the original voters dropped. If a + // past vote is left cached in the system somewhere, this will interfere with + // the final validator outcome. + validators: []string{"A", "B", "C", "D", "E"}, + votes: []testerVote{ + {validator: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed + {validator: "B", voted: "F", auth: true}, + {validator: "C", voted: "F", auth: true}, + {validator: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") + {validator: "E", voted: "F", auth: false}, + {validator: "B", voted: "F", auth: false}, + {validator: "C", voted: "F", auth: false}, + {validator: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed + {validator: "E", voted: "F", auth: true}, + {validator: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed + {validator: "C", voted: "A", auth: false}, + {validator: "D", voted: "A", auth: false}, + {validator: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed + }, + results: []string{"B", "C", "D", "E", "F"}, + }, { + // Epoch transitions reset all votes to allow chain checkpointing + epoch: 3, + validators: []string{"A", "B"}, + votes: []testerVote{ + {validator: "A", voted: "C", auth: true}, + {validator: "B"}, + {validator: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots) + {validator: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, + } + // Run through the scenarios and test them + for i, tt := range tests { + // Create the account pool and generate the initial set of validators + accounts := newTesterAccountPool() + + validators := make([]common.Address, len(tt.validators)) + for j, validator := range tt.validators { + validators[j] = accounts.address(validator) + } + for j := 0; j < len(validators); j++ { + for k := j + 1; k < len(validators); k++ { + if bytes.Compare(validators[j][:], validators[k][:]) > 0 { + validators[j], validators[k] = validators[k], validators[j] + } + } + } + // Create the genesis block with the initial set of validators + genesis := &core.Genesis{ + Difficulty: defaultDifficulty, + Mixhash: types.IstanbulDigest, + } + b := genesis.ToBlock(nil) + extra, _ := prepareExtra(b.Header(), validators) + genesis.ExtraData = extra + // Create a pristine blockchain with the genesis injected + db := rawdb.NewMemoryDatabase() + genesis.Commit(db) + + config := istanbul.DefaultConfig + if tt.epoch != 0 { + config.Epoch = tt.epoch + } + engine := New(config, accounts.accounts[tt.validators[0]], db).(*backend) + chain, _ := core.NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil, nil) + + // Assemble a chain of headers from the cast votes + headers := make([]*types.Header, len(tt.votes)) + for j, vote := range tt.votes { + headers[j] = &types.Header{ + Number: big.NewInt(int64(j) + 1), + Time: uint64(int64(j) * int64(config.BlockPeriod)), + Coinbase: accounts.address(vote.voted), + Difficulty: defaultDifficulty, + MixDigest: types.IstanbulDigest, + } + extra, _ := prepareExtra(headers[j], validators) + headers[j].Extra = extra + if j > 0 { + headers[j].ParentHash = headers[j-1].Hash() + } + if vote.auth { + copy(headers[j].Nonce[:], nonceAuthVote) + } + copy(headers[j].Extra, genesis.ExtraData) + accounts.sign(headers[j], vote.validator) + } + // Pass all the headers through clique and ensure tallying succeeds + head := headers[len(headers)-1] + + snap, err := engine.snapshot(chain, head.Number.Uint64(), head.Hash(), headers) + if err != nil { + t.Errorf("test %d: failed to create voting snapshot: %v", i, err) + continue + } + // Verify the final list of validators against the expected ones + validators = make([]common.Address, len(tt.results)) + for j, validator := range tt.results { + validators[j] = accounts.address(validator) + } + for j := 0; j < len(validators); j++ { + for k := j + 1; k < len(validators); k++ { + if bytes.Compare(validators[j][:], validators[k][:]) > 0 { + validators[j], validators[k] = validators[k], validators[j] + } + } + } + result := snap.validators() + if len(result) != len(validators) { + t.Errorf("test %d: validators mismatch: have %x, want %x", i, result, validators) + continue + } + for j := 0; j < len(result); j++ { + if !bytes.Equal(result[j][:], validators[j][:]) { + t.Errorf("test %d, validator %d: validator mismatch: have %x, want %x", i, j, result[j], validators[j]) + } + } + } +} + +func TestSaveAndLoad(t *testing.T) { + snap := &Snapshot{ + Epoch: 5, + Number: 10, + Hash: common.HexToHash("1234567890"), + Votes: []*Vote{ + { + Validator: common.StringToAddress("1234567891"), + Block: 15, + Address: common.StringToAddress("1234567892"), + Authorize: false, + }, + }, + Tally: map[common.Address]Tally{ + common.StringToAddress("1234567893"): { + Authorize: false, + Votes: 20, + }, + }, + ValSet: validator.NewSet([]common.Address{ + common.StringToAddress("1234567894"), + common.StringToAddress("1234567895"), + }, istanbul.RoundRobin), + } + db := rawdb.NewMemoryDatabase() + err := snap.store(db) + if err != nil { + t.Errorf("store snapshot failed: %v", err) + } + + snap1, err := loadSnapshot(snap.Epoch, db, snap.Hash) + if err != nil { + t.Errorf("load snapshot failed: %v", err) + } + if snap.Epoch != snap1.Epoch { + t.Errorf("epoch mismatch: have %v, want %v", snap1.Epoch, snap.Epoch) + } + if snap.Hash != snap1.Hash { + t.Errorf("hash mismatch: have %v, want %v", snap1.Number, snap.Number) + } + if !reflect.DeepEqual(snap.Votes, snap.Votes) { + t.Errorf("votes mismatch: have %v, want %v", snap1.Votes, snap.Votes) + } + if !reflect.DeepEqual(snap.Tally, snap.Tally) { + t.Errorf("tally mismatch: have %v, want %v", snap1.Tally, snap.Tally) + } + if !reflect.DeepEqual(snap.ValSet, snap.ValSet) { + t.Errorf("validator set mismatch: have %v, want %v", snap1.ValSet, snap.ValSet) + } +} diff --git a/consensus/istanbul/config.go b/consensus/istanbul/config.go new file mode 100644 index 0000000000..dfe6c087d0 --- /dev/null +++ b/consensus/istanbul/config.go @@ -0,0 +1,44 @@ +// Copyright 2017 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 . + +package istanbul + +import "math/big" + +type ProposerPolicy uint64 + +const ( + RoundRobin ProposerPolicy = iota + Sticky +) + +type Config struct { + RequestTimeout uint64 `toml:",omitempty"` // The timeout for each Istanbul round in milliseconds. + BlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between two consecutive block's timestamps in second + ProposerPolicy ProposerPolicy `toml:",omitempty"` // The policy for proposer selection + Epoch uint64 `toml:",omitempty"` // The number of blocks after which to checkpoint and reset the pending votes + Ceil2Nby3Block *big.Int `toml:",omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)] + AllowedFutureBlockTime uint64 `toml:",omitempty"` // Max time (in seconds) from current time allowed for blocks, before they're considered future blocks +} + +var DefaultConfig = &Config{ + RequestTimeout: 10000, + BlockPeriod: 1, + ProposerPolicy: RoundRobin, + Epoch: 30000, + Ceil2Nby3Block: big.NewInt(0), + AllowedFutureBlockTime: 0, +} diff --git a/consensus/istanbul/core/backlog.go b/consensus/istanbul/core/backlog.go new file mode 100644 index 0000000000..ac62c37e71 --- /dev/null +++ b/consensus/istanbul/core/backlog.go @@ -0,0 +1,188 @@ +// Copyright 2017 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 . + +package core + +import ( + "github.com/ethereum/go-ethereum/consensus/istanbul" + "gopkg.in/karalabe/cookiejar.v2/collections/prque" +) + +var ( + // msgPriority is defined for calculating processing priority to speedup consensus + // msgPreprepare > msgCommit > msgPrepare + msgPriority = map[uint64]int{ + msgPreprepare: 1, + msgCommit: 2, + msgPrepare: 3, + } +) + +// checkMessage checks the message state +// return errInvalidMessage if the message is invalid +// return errFutureMessage if the message view is larger than current view +// return errOldMessage if the message view is smaller than current view +func (c *core) checkMessage(msgCode uint64, view *istanbul.View) error { + if view == nil || view.Sequence == nil || view.Round == nil { + return errInvalidMessage + } + + if msgCode == msgRoundChange { + if view.Sequence.Cmp(c.currentView().Sequence) > 0 { + return errFutureMessage + } else if view.Cmp(c.currentView()) < 0 { + return errOldMessage + } + return nil + } + + if view.Cmp(c.currentView()) > 0 { + return errFutureMessage + } + + if view.Cmp(c.currentView()) < 0 { + return errOldMessage + } + + if c.waitingForRoundChange { + return errFutureMessage + } + + // StateAcceptRequest only accepts msgPreprepare + // other messages are future messages + if c.state == StateAcceptRequest { + if msgCode > msgPreprepare { + return errFutureMessage + } + return nil + } + + // For states(StatePreprepared, StatePrepared, StateCommitted), + // can accept all message types if processing with same view + return nil +} + +func (c *core) storeBacklog(msg *message, src istanbul.Validator) { + logger := c.logger.New("from", src, "state", c.state) + + if src.Address() == c.Address() { + logger.Warn("Backlog from self") + return + } + + logger.Trace("Store future message") + + c.backlogsMu.Lock() + defer c.backlogsMu.Unlock() + + logger.Debug("Retrieving backlog queue", "for", src.Address(), "backlogs_size", len(c.backlogs)) + backlog := c.backlogs[src.Address()] + if backlog == nil { + backlog = prque.New() + } + switch msg.Code { + case msgPreprepare: + var p *istanbul.Preprepare + err := msg.Decode(&p) + if err == nil { + backlog.Push(msg, toPriority(msg.Code, p.View)) + } + // for msgRoundChange, msgPrepare and msgCommit cases + default: + var p *istanbul.Subject + err := msg.Decode(&p) + if err == nil { + backlog.Push(msg, toPriority(msg.Code, p.View)) + } + } + c.backlogs[src.Address()] = backlog +} + +func (c *core) processBacklog() { + c.backlogsMu.Lock() + defer c.backlogsMu.Unlock() + + for srcAddress, backlog := range c.backlogs { + if backlog == nil { + continue + } + _, src := c.valSet.GetByAddress(srcAddress) + if src == nil { + // validator is not available + delete(c.backlogs, srcAddress) + continue + } + logger := c.logger.New("from", src, "state", c.state) + isFuture := false + + // We stop processing if + // 1. backlog is empty + // 2. The first message in queue is a future message + for !(backlog.Empty() || isFuture) { + m, prio := backlog.Pop() + msg := m.(*message) + var view *istanbul.View + switch msg.Code { + case msgPreprepare: + var m *istanbul.Preprepare + err := msg.Decode(&m) + if err == nil { + view = m.View + } + // for msgRoundChange, msgPrepare and msgCommit cases + default: + var sub *istanbul.Subject + err := msg.Decode(&sub) + if err == nil { + view = sub.View + } + } + if view == nil { + logger.Debug("Nil view", "msg", msg) + continue + } + // Push back if it's a future message + err := c.checkMessage(msg.Code, view) + if err != nil { + if err == errFutureMessage { + logger.Trace("Stop processing backlog", "msg", msg) + backlog.Push(msg, prio) + isFuture = true + break + } + logger.Trace("Skip the backlog event", "msg", msg, "err", err) + continue + } + logger.Trace("Post backlog event", "msg", msg) + + go c.sendEvent(backlogEvent{ + src: src, + msg: msg, + }) + } + } +} + +func toPriority(msgCode uint64, view *istanbul.View) float32 { + if msgCode == msgRoundChange { + // For msgRoundChange, set the message priority based on its sequence + return -float32(view.Sequence.Uint64() * 1000) + } + // FIXME: round will be reset as 0 while new sequence + // 10 * Round limits the range of message code is from 0 to 9 + // 1000 * Sequence limits the range of round is from 0 to 99 + return -float32(view.Sequence.Uint64()*1000 + view.Round.Uint64()*10 + uint64(msgPriority[msgCode])) +} diff --git a/consensus/istanbul/core/backlog_test.go b/consensus/istanbul/core/backlog_test.go new file mode 100644 index 0000000000..64e5e5ec5b --- /dev/null +++ b/consensus/istanbul/core/backlog_test.go @@ -0,0 +1,364 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "reflect" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/karalabe/cookiejar.v2/collections/prque" +) + +func TestCheckMessage(t *testing.T) { + c := &core{ + state: StateAcceptRequest, + current: newRoundState(&istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(0), + }, newTestValidatorSet(4), common.Hash{}, nil, nil, nil), + } + + // invalid view format + err := c.checkMessage(msgPreprepare, nil) + if err != errInvalidMessage { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidMessage) + } + + testStates := []State{StateAcceptRequest, StatePreprepared, StatePrepared, StateCommitted} + testCode := []uint64{msgPreprepare, msgPrepare, msgCommit, msgRoundChange} + + // future sequence + v := &istanbul.View{ + Sequence: big.NewInt(2), + Round: big.NewInt(0), + } + for i := 0; i < len(testStates); i++ { + c.state = testStates[i] + for j := 0; j < len(testCode); j++ { + err := c.checkMessage(testCode[j], v) + if err != errFutureMessage { + t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage) + } + } + } + + // future round + v = &istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(1), + } + for i := 0; i < len(testStates); i++ { + c.state = testStates[i] + for j := 0; j < len(testCode); j++ { + err := c.checkMessage(testCode[j], v) + if testCode[j] == msgRoundChange { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else if err != errFutureMessage { + t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage) + } + } + } + + // current view but waiting for round change + v = &istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(0), + } + c.waitingForRoundChange = true + for i := 0; i < len(testStates); i++ { + c.state = testStates[i] + for j := 0; j < len(testCode); j++ { + err := c.checkMessage(testCode[j], v) + if testCode[j] == msgRoundChange { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else if err != errFutureMessage { + t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage) + } + } + } + c.waitingForRoundChange = false + + v = c.currentView() + // current view, state = StateAcceptRequest + c.state = StateAcceptRequest + for i := 0; i < len(testCode); i++ { + err = c.checkMessage(testCode[i], v) + if testCode[i] == msgRoundChange { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else if testCode[i] == msgPreprepare { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else { + if err != errFutureMessage { + t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage) + } + } + } + + // current view, state = StatePreprepared + c.state = StatePreprepared + for i := 0; i < len(testCode); i++ { + err = c.checkMessage(testCode[i], v) + if testCode[i] == msgRoundChange { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } + + // current view, state = StatePrepared + c.state = StatePrepared + for i := 0; i < len(testCode); i++ { + err = c.checkMessage(testCode[i], v) + if testCode[i] == msgRoundChange { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } + + // current view, state = StateCommitted + c.state = StateCommitted + for i := 0; i < len(testCode); i++ { + err = c.checkMessage(testCode[i], v) + if testCode[i] == msgRoundChange { + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } else if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + } + +} + +func TestStoreBacklog(t *testing.T) { + c := &core{ + logger: log.New("backend", "test", "id", 0), + valSet: newTestValidatorSet(1), + backlogs: make(map[common.Address]*prque.Prque), + backlogsMu: new(sync.Mutex), + } + v := &istanbul.View{ + Round: big.NewInt(10), + Sequence: big.NewInt(10), + } + p := c.valSet.GetByIndex(0) + // push preprepare msg + preprepare := &istanbul.Preprepare{ + View: v, + Proposal: makeBlock(1), + } + prepreparePayload, _ := Encode(preprepare) + m := &message{ + Code: msgPreprepare, + Msg: prepreparePayload, + } + c.storeBacklog(m, p) + msg := c.backlogs[p.Address()].PopItem() + if !reflect.DeepEqual(msg, m) { + t.Errorf("message mismatch: have %v, want %v", msg, m) + } + + // push prepare msg + subject := &istanbul.Subject{ + View: v, + Digest: common.StringToHash("1234567890"), + } + subjectPayload, _ := Encode(subject) + + m = &message{ + Code: msgPrepare, + Msg: subjectPayload, + } + c.storeBacklog(m, p) + msg = c.backlogs[p.Address()].PopItem() + if !reflect.DeepEqual(msg, m) { + t.Errorf("message mismatch: have %v, want %v", msg, m) + } + + // push commit msg + m = &message{ + Code: msgCommit, + Msg: subjectPayload, + } + c.storeBacklog(m, p) + msg = c.backlogs[p.Address()].PopItem() + if !reflect.DeepEqual(msg, m) { + t.Errorf("message mismatch: have %v, want %v", msg, m) + } + + // push roundChange msg + m = &message{ + Code: msgRoundChange, + Msg: subjectPayload, + } + c.storeBacklog(m, p) + msg = c.backlogs[p.Address()].PopItem() + if !reflect.DeepEqual(msg, m) { + t.Errorf("message mismatch: have %v, want %v", msg, m) + } +} + +func TestProcessFutureBacklog(t *testing.T) { + backend := &testSystemBackend{ + events: new(event.TypeMux), + } + c := &core{ + logger: log.New("backend", "test", "id", 0), + valSet: newTestValidatorSet(1), + backlogs: make(map[common.Address]*prque.Prque), + backlogsMu: new(sync.Mutex), + backend: backend, + current: newRoundState(&istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(0), + }, newTestValidatorSet(4), common.Hash{}, nil, nil, nil), + state: StateAcceptRequest, + } + c.subscribeEvents() + defer c.unsubscribeEvents() + + v := &istanbul.View{ + Round: big.NewInt(10), + Sequence: big.NewInt(10), + } + p := c.valSet.GetByIndex(0) + // push a future msg + subject := &istanbul.Subject{ + View: v, + Digest: common.StringToHash("1234567890"), + } + subjectPayload, _ := Encode(subject) + m := &message{ + Code: msgCommit, + Msg: subjectPayload, + } + c.storeBacklog(m, p) + c.processBacklog() + + const timeoutDura = 2 * time.Second + timeout := time.NewTimer(timeoutDura) + select { + case e, ok := <-c.events.Chan(): + if !ok { + return + } + t.Errorf("unexpected events comes: %v", e) + case <-timeout.C: + // success + } +} + +func TestProcessBacklog(t *testing.T) { + v := &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(1), + } + preprepare := &istanbul.Preprepare{ + View: v, + Proposal: makeBlock(1), + } + prepreparePayload, _ := Encode(preprepare) + + subject := &istanbul.Subject{ + View: v, + Digest: common.StringToHash("1234567890"), + } + subjectPayload, _ := Encode(subject) + + msgs := []*message{ + { + Code: msgPreprepare, + Msg: prepreparePayload, + }, + { + Code: msgPrepare, + Msg: subjectPayload, + }, + { + Code: msgCommit, + Msg: subjectPayload, + }, + { + Code: msgRoundChange, + Msg: subjectPayload, + }, + } + for i := 0; i < len(msgs); i++ { + testProcessBacklog(t, msgs[i]) + } +} + +func testProcessBacklog(t *testing.T, msg *message) { + vset := newTestValidatorSet(1) + backend := &testSystemBackend{ + events: new(event.TypeMux), + peers: vset, + } + c := &core{ + logger: log.New("backend", "test", "id", 0), + backlogs: make(map[common.Address]*prque.Prque), + backlogsMu: new(sync.Mutex), + valSet: vset, + backend: backend, + state: State(msg.Code), + current: newRoundState(&istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(0), + }, newTestValidatorSet(4), common.Hash{}, nil, nil, nil), + } + c.subscribeEvents() + defer c.unsubscribeEvents() + + c.storeBacklog(msg, vset.GetByIndex(0)) + c.processBacklog() + + const timeoutDura = 2 * time.Second + timeout := time.NewTimer(timeoutDura) + select { + case ev := <-c.events.Chan(): + e, ok := ev.Data.(backlogEvent) + if !ok { + t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) + } + if e.msg.Code != msg.Code { + t.Errorf("message code mismatch: have %v, want %v", e.msg.Code, msg.Code) + } + // success + case <-timeout.C: + t.Error("unexpected timeout occurs") + } +} diff --git a/consensus/istanbul/core/commit.go b/consensus/istanbul/core/commit.go new file mode 100644 index 0000000000..166d1925eb --- /dev/null +++ b/consensus/istanbul/core/commit.go @@ -0,0 +1,107 @@ +// Copyright 2017 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 . + +package core + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func (c *core) sendCommit() { + sub := c.current.Subject() + c.broadcastCommit(sub) +} + +func (c *core) sendCommitForOldBlock(view *istanbul.View, digest common.Hash) { + sub := &istanbul.Subject{ + View: view, + Digest: digest, + } + c.broadcastCommit(sub) +} + +func (c *core) broadcastCommit(sub *istanbul.Subject) { + logger := c.logger.New("state", c.state) + + encodedSubject, err := Encode(sub) + if err != nil { + logger.Error("Failed to encode", "subject", sub) + return + } + c.broadcast(&message{ + Code: msgCommit, + Msg: encodedSubject, + }) +} + +func (c *core) handleCommit(msg *message, src istanbul.Validator) error { + // Decode COMMIT message + var commit *istanbul.Subject + err := msg.Decode(&commit) + if err != nil { + return errFailedDecodeCommit + } + + if err := c.checkMessage(msgCommit, commit.View); err != nil { + return err + } + + if err := c.verifyCommit(commit, src); err != nil { + return err + } + + c.acceptCommit(msg, src) + + // Commit the proposal once we have enough COMMIT messages and we are not in the Committed state. + // + // If we already have a proposal, we may have chance to speed up the consensus process + // by committing the proposal without PREPARE messages. + if c.current.Commits.Size() >= c.QuorumSize() && c.state.Cmp(StateCommitted) < 0 { + // Still need to call LockHash here since state can skip Prepared state and jump directly to the Committed state. + c.current.LockHash() + c.commit() + } + + return nil +} + +// verifyCommit verifies if the received COMMIT message is equivalent to our subject +func (c *core) verifyCommit(commit *istanbul.Subject, src istanbul.Validator) error { + logger := c.logger.New("from", src, "state", c.state) + + sub := c.current.Subject() + if !reflect.DeepEqual(commit, sub) { + logger.Warn("Inconsistent subjects between commit and proposal", "expected", sub, "got", commit) + return errInconsistentSubject + } + + return nil +} + +func (c *core) acceptCommit(msg *message, src istanbul.Validator) error { + logger := c.logger.New("from", src, "state", c.state) + + // Add the COMMIT message to current round state + if err := c.current.Commits.Add(msg); err != nil { + logger.Error("Failed to record commit message", "msg", msg, "err", err) + return err + } + + return nil +} diff --git a/consensus/istanbul/core/commit_test.go b/consensus/istanbul/core/commit_test.go new file mode 100644 index 0000000000..9e74893318 --- /dev/null +++ b/consensus/istanbul/core/commit_test.go @@ -0,0 +1,325 @@ +// Copyright 2017 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 . + +package core + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestHandleCommit(t *testing.T) { + N := uint64(4) + F := uint64(1) + + proposal := newTestProposal() + expectedSubject := &istanbul.Subject{ + View: &istanbul.View{ + Round: big.NewInt(0), + Sequence: proposal.Number(), + }, + Digest: proposal.Hash(), + } + + testCases := []struct { + system *testSystem + expectedErr error + }{ + { + // normal case + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(1), + }, + c.valSet, + ) + + if i == 0 { + // replica 0 is the proposer + c.state = StatePrepared + } + } + return sys + }(), + nil, + }, + { + // future message + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i == 0 { + // replica 0 is the proposer + c.current = newTestRoundState( + expectedSubject.View, + c.valSet, + ) + c.state = StatePreprepared + } else { + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(2), + Sequence: big.NewInt(3), + }, + c.valSet, + ) + } + } + return sys + }(), + errFutureMessage, + }, + { + // subject not match + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i == 0 { + // replica 0 is the proposer + c.current = newTestRoundState( + expectedSubject.View, + c.valSet, + ) + c.state = StatePreprepared + } else { + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(0), + }, + c.valSet, + ) + } + } + return sys + }(), + errOldMessage, + }, + { + // jump state + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: proposal.Number(), + }, + c.valSet, + ) + + // only replica0 stays at StatePreprepared + // other replicas are at StatePrepared + if i != 0 { + c.state = StatePrepared + } else { + c.state = StatePreprepared + } + } + return sys + }(), + nil, + }, + // TODO: double send message + } + +OUTER: + for _, test := range testCases { + test.system.Run(false) + + v0 := test.system.backends[0] + r0 := v0.engine.(*core) + + for i, v := range test.system.backends { + validator := r0.valSet.GetByIndex(uint64(i)) + m, _ := Encode(v.engine.(*core).current.Subject()) + if err := r0.handleCommit(&message{ + Code: msgCommit, + Msg: m, + Address: validator.Address(), + Signature: []byte{}, + CommittedSeal: validator.Address().Bytes(), // small hack + }, validator); err != nil { + if err != test.expectedErr { + t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) + } + if r0.current.IsHashLocked() { + t.Errorf("block should not be locked") + } + continue OUTER + } + } + + // prepared is normal case + if r0.state != StateCommitted { + // There are not enough commit messages in core + if r0.state != StatePrepared { + t.Errorf("state mismatch: have %v, want %v", r0.state, StatePrepared) + } + if r0.current.Commits.Size() >= r0.QuorumSize() { + t.Errorf("the size of commit messages should be less than %v", r0.QuorumSize()) + } + if r0.current.IsHashLocked() { + t.Errorf("block should not be locked") + } + continue + } + + // core should have 2F+1 before Ceil2Nby3Block or Ceil(2N/3) prepare messages + if r0.current.Commits.Size() < r0.QuorumSize() { + t.Errorf("the size of commit messages should be larger than 2F+1 or Ceil(2N/3): size %v", r0.QuorumSize()) + } + + // check signatures large than F + signedCount := 0 + committedSeals := v0.committedMsgs[0].committedSeals + for _, validator := range r0.valSet.List() { + for _, seal := range committedSeals { + if bytes.Equal(validator.Address().Bytes(), seal[:common.AddressLength]) { + signedCount++ + break + } + } + } + if signedCount <= r0.valSet.F() { + t.Errorf("the expected signed count should be larger than %v, but got %v", r0.valSet.F(), signedCount) + } + if !r0.current.IsHashLocked() { + t.Errorf("block should be locked") + } + } +} + +// round is not checked for now +func TestVerifyCommit(t *testing.T) { + // for log purpose + privateKey, _ := crypto.GenerateKey() + peer := validator.New(getPublicKeyAddress(privateKey)) + valSet := validator.NewSet([]common.Address{peer.Address()}, istanbul.RoundRobin) + + sys := NewTestSystemWithBackend(uint64(1), uint64(0)) + + testCases := []struct { + expected error + commit *istanbul.Subject + roundState *roundState + }{ + { + // normal case + expected: nil, + commit: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + valSet, + ), + }, + { + // old message + expected: errInconsistentSubject, + commit: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, + valSet, + ), + }, + { + // different digest + expected: errInconsistentSubject, + commit: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + Digest: common.StringToHash("1234567890"), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, + valSet, + ), + }, + { + // malicious package(lack of sequence) + expected: errInconsistentSubject, + commit: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: nil}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, + valSet, + ), + }, + { + // wrong prepare message with same sequence but different round + expected: errInconsistentSubject, + commit: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(0)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + valSet, + ), + }, + { + // wrong prepare message with same round but different sequence + expected: errInconsistentSubject, + commit: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(1)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + valSet, + ), + }, + } + for i, test := range testCases { + c := sys.backends[0].engine.(*core) + c.current = test.roundState + + if err := c.verifyCommit(test.commit, peer); err != nil { + if err != test.expected { + t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected) + } + } + } +} diff --git a/consensus/istanbul/core/core.go b/consensus/istanbul/core/core.go new file mode 100644 index 0000000000..121067d04b --- /dev/null +++ b/consensus/istanbul/core/core.go @@ -0,0 +1,359 @@ +// Copyright 2017 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 . + +package core + +import ( + "bytes" + "math" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + metrics "github.com/ethereum/go-ethereum/metrics" + "gopkg.in/karalabe/cookiejar.v2/collections/prque" +) + +// New creates an Istanbul consensus core +func New(backend istanbul.Backend, config *istanbul.Config) Engine { + r := metrics.NewRegistry() + c := &core{ + config: config, + address: backend.Address(), + state: StateAcceptRequest, + handlerWg: new(sync.WaitGroup), + logger: log.New("address", backend.Address()), + backend: backend, + backlogs: make(map[common.Address]*prque.Prque), + backlogsMu: new(sync.Mutex), + pendingRequests: prque.New(), + pendingRequestsMu: new(sync.Mutex), + consensusTimestamp: time.Time{}, + roundMeter: metrics.NewMeter(), + sequenceMeter: metrics.NewMeter(), + consensusTimer: metrics.NewTimer(), + } + + r.Register("consensus/istanbul/core/round", c.roundMeter) + r.Register("consensus/istanbul/core/sequence", c.sequenceMeter) + r.Register("consensus/istanbul/core/consensus", c.consensusTimer) + + c.validateFn = c.checkValidatorSignature + return c +} + +// ---------------------------------------------------------------------------- + +type core struct { + config *istanbul.Config + address common.Address + state State + logger log.Logger + + backend istanbul.Backend + events *event.TypeMuxSubscription + finalCommittedSub *event.TypeMuxSubscription + timeoutSub *event.TypeMuxSubscription + futurePreprepareTimer *time.Timer + + valSet istanbul.ValidatorSet + waitingForRoundChange bool + validateFn func([]byte, []byte) (common.Address, error) + + backlogs map[common.Address]*prque.Prque + backlogsMu *sync.Mutex + + current *roundState + handlerWg *sync.WaitGroup + + roundChangeSet *roundChangeSet + roundChangeTimer *time.Timer + + pendingRequests *prque.Prque + pendingRequestsMu *sync.Mutex + + consensusTimestamp time.Time + // the meter to record the round change rate + roundMeter metrics.Meter + // the meter to record the sequence update rate + sequenceMeter metrics.Meter + // the timer to record consensus duration (from accepting a preprepare to final committed stage) + consensusTimer metrics.Timer +} + +func (c *core) finalizeMessage(msg *message) ([]byte, error) { + var err error + // Add sender address + msg.Address = c.Address() + + // Add proof of consensus + msg.CommittedSeal = []byte{} + // Assign the CommittedSeal if it's a COMMIT message and proposal is not nil + if msg.Code == msgCommit && c.current.Proposal() != nil { + seal := PrepareCommittedSeal(c.current.Proposal().Hash()) + msg.CommittedSeal, err = c.backend.Sign(seal) + if err != nil { + return nil, err + } + } + + // Sign message + data, err := msg.PayloadNoSig() + if err != nil { + return nil, err + } + msg.Signature, err = c.backend.Sign(data) + if err != nil { + return nil, err + } + + // Convert to payload + payload, err := msg.Payload() + if err != nil { + return nil, err + } + + return payload, nil +} + +func (c *core) broadcast(msg *message) { + logger := c.logger.New("state", c.state) + + payload, err := c.finalizeMessage(msg) + if err != nil { + logger.Error("Failed to finalize message", "msg", msg, "err", err) + return + } + + // Broadcast payload + if err = c.backend.Broadcast(c.valSet, payload); err != nil { + logger.Error("Failed to broadcast message", "msg", msg, "err", err) + return + } +} + +func (c *core) currentView() *istanbul.View { + return &istanbul.View{ + Sequence: new(big.Int).Set(c.current.Sequence()), + Round: new(big.Int).Set(c.current.Round()), + } +} + +func (c *core) IsProposer() bool { + v := c.valSet + if v == nil { + return false + } + return v.IsProposer(c.backend.Address()) +} + +func (c *core) IsCurrentProposal(blockHash common.Hash) bool { + return c.current != nil && c.current.pendingRequest != nil && c.current.pendingRequest.Proposal.Hash() == blockHash +} + +func (c *core) commit() { + c.setState(StateCommitted) + + proposal := c.current.Proposal() + if proposal != nil { + committedSeals := make([][]byte, c.current.Commits.Size()) + for i, v := range c.current.Commits.Values() { + committedSeals[i] = make([]byte, types.IstanbulExtraSeal) + copy(committedSeals[i][:], v.CommittedSeal[:]) + } + + if err := c.backend.Commit(proposal, committedSeals); err != nil { + c.current.UnlockHash() //Unlock block when insertion fails + c.sendNextRoundChange() + return + } + } +} + +// startNewRound starts a new round. if round equals to 0, it means to starts a new sequence +func (c *core) startNewRound(round *big.Int) { + var logger log.Logger + if c.current == nil { + logger = c.logger.New("old_round", -1, "old_seq", 0) + } else { + logger = c.logger.New("old_round", c.current.Round(), "old_seq", c.current.Sequence()) + } + + roundChange := false + // Try to get last proposal + lastProposal, lastProposer := c.backend.LastProposal() + if c.current == nil { + logger.Trace("Start to the initial round") + } else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 { + diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence()) + c.sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64()) + + if !c.consensusTimestamp.IsZero() { + c.consensusTimer.UpdateSince(c.consensusTimestamp) + c.consensusTimestamp = time.Time{} + } + logger.Trace("Catch up latest proposal", "number", lastProposal.Number().Uint64(), "hash", lastProposal.Hash()) + } else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 { + if round.Cmp(common.Big0) == 0 { + // same seq and round, don't need to start new round + return + } else if round.Cmp(c.current.Round()) < 0 { + logger.Warn("New round should not be smaller than current round", "seq", lastProposal.Number().Int64(), "new_round", round, "old_round", c.current.Round()) + return + } + roundChange = true + } else { + logger.Warn("New sequence should be larger than current sequence", "new_seq", lastProposal.Number().Int64()) + return + } + + var newView *istanbul.View + if roundChange { + newView = &istanbul.View{ + Sequence: new(big.Int).Set(c.current.Sequence()), + Round: new(big.Int).Set(round), + } + } else { + newView = &istanbul.View{ + Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1), + Round: new(big.Int), + } + c.valSet = c.backend.Validators(lastProposal) + } + + // Update logger + logger = logger.New("old_proposer", c.valSet.GetProposer()) + // Clear invalid ROUND CHANGE messages + c.roundChangeSet = newRoundChangeSet(c.valSet) + // New snapshot for new round + c.updateRoundState(newView, c.valSet, roundChange) + // Calculate new proposer + c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) + c.waitingForRoundChange = false + c.setState(StateAcceptRequest) + if roundChange && c.IsProposer() && c.current != nil { + // If it is locked, propose the old proposal + // If we have pending request, propose pending request + if c.current.IsHashLocked() { + r := &istanbul.Request{ + Proposal: c.current.Proposal(), //c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState + } + c.sendPreprepare(r) + } else if c.current.pendingRequest != nil { + c.sendPreprepare(c.current.pendingRequest) + } + } + c.newRoundChangeTimer() + + logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "IsProposer", c.IsProposer()) +} + +func (c *core) catchUpRound(view *istanbul.View) { + logger := c.logger.New("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.valSet.GetProposer()) + + if view.Round.Cmp(c.current.Round()) > 0 { + c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64()) + } + c.waitingForRoundChange = true + + // Need to keep block locked for round catching up + c.updateRoundState(view, c.valSet, true) + c.roundChangeSet.Clear(view.Round) + c.newRoundChangeTimer() + + logger.Trace("Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet) +} + +// updateRoundState updates round state by checking if locking block is necessary +func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) { + // Lock only if both roundChange is true and it is locked + if roundChange && c.current != nil { + if c.current.IsHashLocked() { + c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal) + } else { + c.current = newRoundState(view, validatorSet, common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal) + } + } else { + c.current = newRoundState(view, validatorSet, common.Hash{}, nil, nil, c.backend.HasBadProposal) + } +} + +func (c *core) setState(state State) { + if c.state != state { + c.state = state + } + if state == StateAcceptRequest { + c.processPendingRequests() + } + c.processBacklog() +} + +func (c *core) Address() common.Address { + return c.address +} + +func (c *core) stopFuturePreprepareTimer() { + if c.futurePreprepareTimer != nil { + c.futurePreprepareTimer.Stop() + } +} + +func (c *core) stopTimer() { + c.stopFuturePreprepareTimer() + if c.roundChangeTimer != nil { + c.roundChangeTimer.Stop() + } +} + +func (c *core) newRoundChangeTimer() { + c.stopTimer() + + // set timeout based on the round number + timeout := time.Duration(c.config.RequestTimeout) * time.Millisecond + round := c.current.Round().Uint64() + if round > 0 { + timeout += time.Duration(math.Pow(2, float64(round))) * time.Second + } + c.roundChangeTimer = time.AfterFunc(timeout, func() { + c.sendEvent(timeoutEvent{}) + }) +} + +func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) { + return istanbul.CheckValidatorSignature(c.valSet, data, sig) +} + +func (c *core) QuorumSize() int { + if c.config.Ceil2Nby3Block == nil || (c.current != nil && c.current.sequence.Cmp(c.config.Ceil2Nby3Block) < 0) { + c.logger.Trace("Confirmation Formula used 2F+ 1") + return (2 * c.valSet.F()) + 1 + } + c.logger.Trace("Confirmation Formula used ceil(2N/3)") + return int(math.Ceil(float64(2*c.valSet.Size()) / 3)) +} + +// PrepareCommittedSeal returns a committed seal for the given hash +func PrepareCommittedSeal(hash common.Hash) []byte { + var buf bytes.Buffer + buf.Write(hash.Bytes()) + buf.Write([]byte{byte(msgCommit)}) + return buf.Bytes() +} diff --git a/consensus/istanbul/core/core_test.go b/consensus/istanbul/core/core_test.go new file mode 100644 index 0000000000..2212d7d863 --- /dev/null +++ b/consensus/istanbul/core/core_test.go @@ -0,0 +1,97 @@ +// Copyright 2017 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 . + +package core + +import ( + "fmt" + "math/big" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/core/types" + elog "github.com/ethereum/go-ethereum/log" +) + +func makeBlock(number int64) *types.Block { + header := &types.Header{ + Difficulty: big.NewInt(0), + Number: big.NewInt(number), + GasLimit: 0, + GasUsed: 0, + Time: 0, + } + block := &types.Block{} + return block.WithSeal(header) +} + +func newTestProposal() istanbul.Proposal { + return makeBlock(1) +} + +func TestNewRequest(t *testing.T) { + testLogger.SetHandler(elog.StdoutHandler) + + N := uint64(4) + F := uint64(1) + + sys := NewTestSystemWithBackend(N, F) + + close := sys.Run(true) + defer close() + + request1 := makeBlock(1) + sys.backends[0].NewRequest(request1) + + <-time.After(1 * time.Second) + + request2 := makeBlock(2) + sys.backends[0].NewRequest(request2) + + <-time.After(1 * time.Second) + + for _, backend := range sys.backends { + if len(backend.committedMsgs) != 2 { + t.Errorf("the number of executed requests mismatch: have %v, want 2", len(backend.committedMsgs)) + } + if !reflect.DeepEqual(request1.Number(), backend.committedMsgs[0].commitProposal.Number()) { + t.Errorf("the number of requests mismatch: have %v, want %v", request1.Number(), backend.committedMsgs[0].commitProposal.Number()) + } + if !reflect.DeepEqual(request2.Number(), backend.committedMsgs[1].commitProposal.Number()) { + t.Errorf("the number of requests mismatch: have %v, want %v", request2.Number(), backend.committedMsgs[1].commitProposal.Number()) + } + } +} + +func TestQuorumSize(t *testing.T) { + N := uint64(4) + F := uint64(1) + + sys := NewTestSystemWithBackend(N, F) + backend := sys.backends[0] + c := backend.engine.(*core) + + valSet := c.valSet + for i := 1; i <= 1000; i++ { + valSet.AddValidator(common.StringToAddress(fmt.Sprint(i))) + if 2*c.QuorumSize() <= (valSet.Size()+valSet.F()) || 2*c.QuorumSize() > (valSet.Size()+valSet.F()+2) { + t.Errorf("quorumSize constraint failed, expected value (2*QuorumSize > Size+F && 2*QuorumSize <= Size+F+2) to be:%v, got: %v, for size: %v", true, false, valSet.Size()) + } + } +} diff --git a/consensus/istanbul/core/errors.go b/consensus/istanbul/core/errors.go new file mode 100644 index 0000000000..23aebba5f5 --- /dev/null +++ b/consensus/istanbul/core/errors.go @@ -0,0 +1,48 @@ +// Copyright 2017 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 . + +package core + +import "errors" + +var ( + // errInconsistentSubject is returned when received subject is different from + // current subject. + errInconsistentSubject = errors.New("inconsistent subjects") + // errNotFromProposer is returned when received message is supposed to be from + // proposer. + errNotFromProposer = errors.New("message does not come from proposer") + // errIgnored is returned when a message was ignored. + errIgnored = errors.New("message is ignored") + // errFutureMessage is returned when current view is earlier than the + // view of the received message. + errFutureMessage = errors.New("future message") + // errOldMessage is returned when the received message's view is earlier + // than current view. + errOldMessage = errors.New("old message") + // errInvalidMessage is returned when the message is malformed. + errInvalidMessage = errors.New("invalid message") + // errFailedDecodePreprepare is returned when the PRE-PREPARE message is malformed. + errFailedDecodePreprepare = errors.New("failed to decode PRE-PREPARE") + // errFailedDecodePrepare is returned when the PREPARE message is malformed. + errFailedDecodePrepare = errors.New("failed to decode PREPARE") + // errFailedDecodeCommit is returned when the COMMIT message is malformed. + errFailedDecodeCommit = errors.New("failed to decode COMMIT") + // errFailedDecodeMessageSet is returned when the message set is malformed. + // errFailedDecodeMessageSet = errors.New("failed to decode message set") + // errInvalidSigner is returned when the message is signed by a validator different than message sender + errInvalidSigner = errors.New("message not signed by the sender") +) diff --git a/consensus/istanbul/core/events.go b/consensus/istanbul/core/events.go new file mode 100644 index 0000000000..c3292fa174 --- /dev/null +++ b/consensus/istanbul/core/events.go @@ -0,0 +1,28 @@ +// Copyright 2017 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 . + +package core + +import ( + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +type backlogEvent struct { + src istanbul.Validator + msg *message +} + +type timeoutEvent struct{} diff --git a/consensus/istanbul/core/final_committed.go b/consensus/istanbul/core/final_committed.go new file mode 100644 index 0000000000..35e84d4f1d --- /dev/null +++ b/consensus/istanbul/core/final_committed.go @@ -0,0 +1,26 @@ +// Copyright 2017 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 . + +package core + +import "github.com/ethereum/go-ethereum/common" + +func (c *core) handleFinalCommitted() error { + logger := c.logger.New("state", c.state) + logger.Trace("Received a final committed proposal") + c.startNewRound(common.Big0) + return nil +} diff --git a/consensus/istanbul/core/handler.go b/consensus/istanbul/core/handler.go new file mode 100644 index 0000000000..299824cd41 --- /dev/null +++ b/consensus/istanbul/core/handler.go @@ -0,0 +1,201 @@ +// Copyright 2017 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 . + +package core + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +// Start implements core.Engine.Start +func (c *core) Start() error { + // Start a new round from last sequence + 1 + c.startNewRound(common.Big0) + + // Tests will handle events itself, so we have to make subscribeEvents() + // be able to call in test. + c.subscribeEvents() + go c.handleEvents() + + return nil +} + +// Stop implements core.Engine.Stop +func (c *core) Stop() error { + c.stopTimer() + c.unsubscribeEvents() + + // Make sure the handler goroutine exits + c.handlerWg.Wait() + return nil +} + +// ---------------------------------------------------------------------------- + +// Subscribe both internal and external events +func (c *core) subscribeEvents() { + c.events = c.backend.EventMux().Subscribe( + // external events + istanbul.RequestEvent{}, + istanbul.MessageEvent{}, + // internal events + backlogEvent{}, + ) + c.timeoutSub = c.backend.EventMux().Subscribe( + timeoutEvent{}, + ) + c.finalCommittedSub = c.backend.EventMux().Subscribe( + istanbul.FinalCommittedEvent{}, + ) +} + +// Unsubscribe all events +func (c *core) unsubscribeEvents() { + c.events.Unsubscribe() + c.timeoutSub.Unsubscribe() + c.finalCommittedSub.Unsubscribe() +} + +func (c *core) handleEvents() { + // Clear state + defer func() { + c.current = nil + c.handlerWg.Done() + }() + + c.handlerWg.Add(1) + for { + select { + case event, ok := <-c.events.Chan(): + if !ok { + return + } + // A real event arrived, process interesting content + switch ev := event.Data.(type) { + case istanbul.RequestEvent: + r := &istanbul.Request{ + Proposal: ev.Proposal, + } + err := c.handleRequest(r) + if err == errFutureMessage { + c.storeRequestMsg(r) + } + case istanbul.MessageEvent: + if err := c.handleMsg(ev.Payload); err == nil { + c.backend.Gossip(c.valSet, ev.Payload) + } + case backlogEvent: + // No need to check signature for internal messages + if err := c.handleCheckedMsg(ev.msg, ev.src); err == nil { + p, err := ev.msg.Payload() + if err != nil { + c.logger.Warn("Get message payload failed", "err", err) + continue + } + c.backend.Gossip(c.valSet, p) + } + } + case _, ok := <-c.timeoutSub.Chan(): + if !ok { + return + } + c.handleTimeoutMsg() + case event, ok := <-c.finalCommittedSub.Chan(): + if !ok { + return + } + switch event.Data.(type) { + case istanbul.FinalCommittedEvent: + c.handleFinalCommitted() + } + } + } +} + +// sendEvent sends events to mux +func (c *core) sendEvent(ev interface{}) { + c.backend.EventMux().Post(ev) +} + +func (c *core) handleMsg(payload []byte) error { + logger := c.logger.New() + + // Decode message and check its signature + msg := new(message) + if err := msg.FromPayload(payload, c.validateFn); err != nil { + logger.Error("Failed to decode message from payload", "err", err) + return err + } + + // Only accept message if the address is valid + _, src := c.valSet.GetByAddress(msg.Address) + if src == nil { + logger.Error("Invalid address in message", "msg", msg) + return istanbul.ErrUnauthorizedAddress + } + + return c.handleCheckedMsg(msg, src) +} + +func (c *core) handleCheckedMsg(msg *message, src istanbul.Validator) error { + logger := c.logger.New("address", c.address, "from", src) + + // Store the message if it's a future message + testBacklog := func(err error) error { + if err == errFutureMessage { + c.storeBacklog(msg, src) + } + + return err + } + + switch msg.Code { + case msgPreprepare: + return testBacklog(c.handlePreprepare(msg, src)) + case msgPrepare: + return testBacklog(c.handlePrepare(msg, src)) + case msgCommit: + return testBacklog(c.handleCommit(msg, src)) + case msgRoundChange: + return testBacklog(c.handleRoundChange(msg, src)) + default: + logger.Error("Invalid message", "msg", msg) + } + + return errInvalidMessage +} + +func (c *core) handleTimeoutMsg() { + // If we're not waiting for round change yet, we can try to catch up + // the max round with F+1 round change message. We only need to catch up + // if the max round is larger than current round. + if !c.waitingForRoundChange { + maxRound := c.roundChangeSet.MaxRound(c.valSet.F() + 1) + if maxRound != nil && maxRound.Cmp(c.current.Round()) > 0 { + c.sendRoundChange(maxRound) + return + } + } + + lastProposal, _ := c.backend.LastProposal() + if lastProposal != nil && lastProposal.Number().Cmp(c.current.Sequence()) >= 0 { + c.logger.Trace("round change timeout, catch up latest sequence", "number", lastProposal.Number().Uint64()) + c.startNewRound(common.Big0) + } else { + c.sendNextRoundChange() + } +} diff --git a/consensus/istanbul/core/handler_test.go b/consensus/istanbul/core/handler_test.go new file mode 100644 index 0000000000..a54b1f9ccc --- /dev/null +++ b/consensus/istanbul/core/handler_test.go @@ -0,0 +1,127 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +// notice: the normal case have been tested in integration tests. +func TestHandleMsg(t *testing.T) { + N := uint64(4) + F := uint64(1) + sys := NewTestSystemWithBackend(N, F) + + closer := sys.Run(true) + defer closer() + + v0 := sys.backends[0] + r0 := v0.engine.(*core) + + m, _ := Encode(&istanbul.Subject{ + View: &istanbul.View{ + Sequence: big.NewInt(0), + Round: big.NewInt(0), + }, + Digest: common.StringToHash("1234567890"), + }) + // with a matched payload. msgPreprepare should match with *istanbul.Preprepare in normal case. + msg := &message{ + Code: msgPreprepare, + Msg: m, + Address: v0.Address(), + Signature: []byte{}, + CommittedSeal: []byte{}, + } + + _, val := v0.Validators(nil).GetByAddress(v0.Address()) + if err := r0.handleCheckedMsg(msg, val); err != errFailedDecodePreprepare { + t.Errorf("error mismatch: have %v, want %v", err, errFailedDecodePreprepare) + } + + m, _ = Encode(&istanbul.Preprepare{ + View: &istanbul.View{ + Sequence: big.NewInt(0), + Round: big.NewInt(0), + }, + Proposal: makeBlock(1), + }) + // with a unmatched payload. msgPrepare should match with *istanbul.Subject in normal case. + msg = &message{ + Code: msgPrepare, + Msg: m, + Address: v0.Address(), + Signature: []byte{}, + CommittedSeal: []byte{}, + } + + _, val = v0.Validators(nil).GetByAddress(v0.Address()) + if err := r0.handleCheckedMsg(msg, val); err != errFailedDecodePrepare { + t.Errorf("error mismatch: have %v, want %v", err, errFailedDecodePreprepare) + } + + m, _ = Encode(&istanbul.Preprepare{ + View: &istanbul.View{ + Sequence: big.NewInt(0), + Round: big.NewInt(0), + }, + Proposal: makeBlock(2), + }) + // with a unmatched payload. istanbul.MsgCommit should match with *istanbul.Subject in normal case. + msg = &message{ + Code: msgCommit, + Msg: m, + Address: v0.Address(), + Signature: []byte{}, + CommittedSeal: []byte{}, + } + + _, val = v0.Validators(nil).GetByAddress(v0.Address()) + if err := r0.handleCheckedMsg(msg, val); err != errFailedDecodeCommit { + t.Errorf("error mismatch: have %v, want %v", err, errFailedDecodeCommit) + } + + m, _ = Encode(&istanbul.Preprepare{ + View: &istanbul.View{ + Sequence: big.NewInt(0), + Round: big.NewInt(0), + }, + Proposal: makeBlock(3), + }) + // invalid message code. message code is not exists in list + msg = &message{ + Code: uint64(99), + Msg: m, + Address: v0.Address(), + Signature: []byte{}, + CommittedSeal: []byte{}, + } + + _, val = v0.Validators(nil).GetByAddress(v0.Address()) + if err := r0.handleCheckedMsg(msg, val); err == nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + // with malicious payload + if err := r0.handleMsg([]byte{1}); err == nil { + t.Errorf("error mismatch: have %v, want nil", err) + } +} diff --git a/consensus/istanbul/core/message_set.go b/consensus/istanbul/core/message_set.go new file mode 100644 index 0000000000..82b1c06776 --- /dev/null +++ b/consensus/istanbul/core/message_set.go @@ -0,0 +1,115 @@ +// Copyright 2017 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 . + +package core + +import ( + "fmt" + "math/big" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +// Construct a new message set to accumulate messages for given sequence/view number. +func newMessageSet(valSet istanbul.ValidatorSet) *messageSet { + return &messageSet{ + view: &istanbul.View{ + Round: new(big.Int), + Sequence: new(big.Int), + }, + messagesMu: new(sync.Mutex), + messages: make(map[common.Address]*message), + valSet: valSet, + } +} + +// ---------------------------------------------------------------------------- + +type messageSet struct { + view *istanbul.View + valSet istanbul.ValidatorSet + messagesMu *sync.Mutex + messages map[common.Address]*message +} + +func (ms *messageSet) View() *istanbul.View { + return ms.view +} + +func (ms *messageSet) Add(msg *message) error { + ms.messagesMu.Lock() + defer ms.messagesMu.Unlock() + + if err := ms.verify(msg); err != nil { + return err + } + + return ms.addVerifiedMessage(msg) +} + +func (ms *messageSet) Values() (result []*message) { + ms.messagesMu.Lock() + defer ms.messagesMu.Unlock() + + for _, v := range ms.messages { + result = append(result, v) + } + + return result +} + +func (ms *messageSet) Size() int { + ms.messagesMu.Lock() + defer ms.messagesMu.Unlock() + return len(ms.messages) +} + +func (ms *messageSet) Get(addr common.Address) *message { + ms.messagesMu.Lock() + defer ms.messagesMu.Unlock() + return ms.messages[addr] +} + +// ---------------------------------------------------------------------------- + +func (ms *messageSet) verify(msg *message) error { + // verify if the message comes from one of the validators + if _, v := ms.valSet.GetByAddress(msg.Address); v == nil { + return istanbul.ErrUnauthorizedAddress + } + + // TODO: check view number and sequence number + + return nil +} + +func (ms *messageSet) addVerifiedMessage(msg *message) error { + ms.messages[msg.Address] = msg + return nil +} + +func (ms *messageSet) String() string { + ms.messagesMu.Lock() + defer ms.messagesMu.Unlock() + addresses := make([]string, 0, len(ms.messages)) + for _, v := range ms.messages { + addresses = append(addresses, v.Address.String()) + } + return fmt.Sprintf("[%v]", strings.Join(addresses, ", ")) +} diff --git a/consensus/istanbul/core/message_set_test.go b/consensus/istanbul/core/message_set_test.go new file mode 100644 index 0000000000..bd76b5b10c --- /dev/null +++ b/consensus/istanbul/core/message_set_test.go @@ -0,0 +1,106 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/rlp" +) + +func TestMessageSetWithPreprepare(t *testing.T) { + valSet := newTestValidatorSet(4) + + ms := newMessageSet(valSet) + + view := &istanbul.View{ + Round: new(big.Int), + Sequence: new(big.Int), + } + pp := &istanbul.Preprepare{ + View: view, + Proposal: makeBlock(1), + } + + rawPP, err := rlp.EncodeToBytes(pp) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + msg := &message{ + Code: msgPreprepare, + Msg: rawPP, + Address: valSet.GetProposer().Address(), + } + + err = ms.Add(msg) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + err = ms.Add(msg) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + if ms.Size() != 1 { + t.Errorf("the size of message set mismatch: have %v, want 1", ms.Size()) + } +} + +func TestMessageSetWithSubject(t *testing.T) { + valSet := newTestValidatorSet(4) + + ms := newMessageSet(valSet) + + view := &istanbul.View{ + Round: new(big.Int), + Sequence: new(big.Int), + } + + sub := &istanbul.Subject{ + View: view, + Digest: common.StringToHash("1234567890"), + } + + rawSub, err := rlp.EncodeToBytes(sub) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + msg := &message{ + Code: msgPrepare, + Msg: rawSub, + Address: valSet.GetProposer().Address(), + } + + err = ms.Add(msg) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + err = ms.Add(msg) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + if ms.Size() != 1 { + t.Errorf("the size of message set mismatch: have %v, want 1", ms.Size()) + } +} diff --git a/consensus/istanbul/core/prepare.go b/consensus/istanbul/core/prepare.go new file mode 100644 index 0000000000..4a1154c06e --- /dev/null +++ b/consensus/istanbul/core/prepare.go @@ -0,0 +1,95 @@ +// Copyright 2017 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 . + +package core + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func (c *core) sendPrepare() { + logger := c.logger.New("state", c.state) + + sub := c.current.Subject() + encodedSubject, err := Encode(sub) + if err != nil { + logger.Error("Failed to encode", "subject", sub) + return + } + c.broadcast(&message{ + Code: msgPrepare, + Msg: encodedSubject, + }) +} + +func (c *core) handlePrepare(msg *message, src istanbul.Validator) error { + // Decode PREPARE message + var prepare *istanbul.Subject + err := msg.Decode(&prepare) + if err != nil { + return errFailedDecodePrepare + } + + if err := c.checkMessage(msgPrepare, prepare.View); err != nil { + return err + } + + // If it is locked, it can only process on the locked block. + // Passing verifyPrepare and checkMessage implies it is processing on the locked block since it was verified in the Preprepared state. + if err := c.verifyPrepare(prepare, src); err != nil { + return err + } + + c.acceptPrepare(msg, src) + + // Change to Prepared state if we've received enough PREPARE messages or it is locked + // and we are in earlier state before Prepared state. + if ((c.current.IsHashLocked() && prepare.Digest == c.current.GetLockedHash()) || c.current.GetPrepareOrCommitSize() >= c.QuorumSize()) && + c.state.Cmp(StatePrepared) < 0 { + c.current.LockHash() + c.setState(StatePrepared) + c.sendCommit() + } + + return nil +} + +// verifyPrepare verifies if the received PREPARE message is equivalent to our subject +func (c *core) verifyPrepare(prepare *istanbul.Subject, src istanbul.Validator) error { + logger := c.logger.New("from", src, "state", c.state) + + sub := c.current.Subject() + if !reflect.DeepEqual(prepare, sub) { + logger.Warn("Inconsistent subjects between PREPARE and proposal", "expected", sub, "got", prepare) + return errInconsistentSubject + } + + return nil +} + +func (c *core) acceptPrepare(msg *message, src istanbul.Validator) error { + logger := c.logger.New("from", src, "state", c.state) + + // Add the PREPARE message to current round state + if err := c.current.Prepares.Add(msg); err != nil { + logger.Error("Failed to add PREPARE message to round state", "msg", msg, "err", err) + return err + } + + return nil +} diff --git a/consensus/istanbul/core/prepare_test.go b/consensus/istanbul/core/prepare_test.go new file mode 100644 index 0000000000..ac6ef475c1 --- /dev/null +++ b/consensus/istanbul/core/prepare_test.go @@ -0,0 +1,359 @@ +// Copyright 2017 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 . + +package core + +import ( + "math" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestHandlePrepare(t *testing.T) { + N := uint64(4) + F := uint64(1) + + proposal := newTestProposal() + expectedSubject := &istanbul.Subject{ + View: &istanbul.View{ + Round: big.NewInt(0), + Sequence: proposal.Number(), + }, + Digest: proposal.Hash(), + } + + testCases := []struct { + system *testSystem + expectedErr error + }{ + { + // normal case + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(1), + }, + c.valSet, + ) + + if i == 0 { + // replica 0 is the proposer + c.state = StatePreprepared + } + } + return sys + }(), + nil, + }, + { + // future message + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i == 0 { + // replica 0 is the proposer + c.current = newTestRoundState( + expectedSubject.View, + c.valSet, + ) + c.state = StatePreprepared + } else { + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(2), + Sequence: big.NewInt(3), + }, + c.valSet, + ) + } + } + return sys + }(), + errFutureMessage, + }, + { + // subject not match + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i == 0 { + // replica 0 is the proposer + c.current = newTestRoundState( + expectedSubject.View, + c.valSet, + ) + c.state = StatePreprepared + } else { + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(0), + }, + c.valSet, + ) + } + } + return sys + }(), + errOldMessage, + }, + { + // subject not match + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i == 0 { + // replica 0 is the proposer + c.current = newTestRoundState( + expectedSubject.View, + c.valSet, + ) + c.state = StatePreprepared + } else { + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(1)}, + c.valSet, + ) + } + } + return sys + }(), + errInconsistentSubject, + }, + { + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + // save less than Ceil(2*N/3) replica + sys.backends = sys.backends[int(math.Ceil(float64(2*N)/3)):] + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + c.current = newTestRoundState( + expectedSubject.View, + c.valSet, + ) + + if i == 0 { + // replica 0 is the proposer + c.state = StatePreprepared + } + } + return sys + }(), + nil, + }, + // TODO: double send message + } + +OUTER: + for _, test := range testCases { + test.system.Run(false) + + v0 := test.system.backends[0] + r0 := v0.engine.(*core) + + for i, v := range test.system.backends { + validator := r0.valSet.GetByIndex(uint64(i)) + m, _ := Encode(v.engine.(*core).current.Subject()) + if err := r0.handlePrepare(&message{ + Code: msgPrepare, + Msg: m, + Address: validator.Address(), + }, validator); err != nil { + if err != test.expectedErr { + t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) + } + if r0.current.IsHashLocked() { + t.Errorf("block should not be locked") + } + continue OUTER + } + } + + // prepared is normal case + if r0.state != StatePrepared { + // There are not enough PREPARE messages in core + if r0.state != StatePreprepared { + t.Errorf("state mismatch: have %v, want %v", r0.state, StatePreprepared) + } + if r0.current.Prepares.Size() >= r0.QuorumSize() { + t.Errorf("the size of PREPARE messages should be less than %v", r0.QuorumSize()) + } + if r0.current.IsHashLocked() { + t.Errorf("block should not be locked") + } + + continue + } + + // core should have 2F+1 before Ceil2Nby3Block and Ceil(2N/3) after Ceil2Nby3Block PREPARE messages + if r0.current.Prepares.Size() < r0.QuorumSize() { + t.Errorf("the size of PREPARE messages should be larger than 2F+1 or ceil(2N/3): size %v", r0.current.Commits.Size()) + } + + // a message will be delivered to backend if ceil(2N/3) + if int64(len(v0.sentMsgs)) != 1 { + t.Errorf("the Send() should be called once: times %v", len(test.system.backends[0].sentMsgs)) + } + + // verify COMMIT messages + decodedMsg := new(message) + err := decodedMsg.FromPayload(v0.sentMsgs[0], nil) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + if decodedMsg.Code != msgCommit { + t.Errorf("message code mismatch: have %v, want %v", decodedMsg.Code, msgCommit) + } + var m *istanbul.Subject + err = decodedMsg.Decode(&m) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + if !reflect.DeepEqual(m, expectedSubject) { + t.Errorf("subject mismatch: have %v, want %v", m, expectedSubject) + } + if !r0.current.IsHashLocked() { + t.Errorf("block should be locked") + } + } +} + +// round is not checked for now +func TestVerifyPrepare(t *testing.T) { + // for log purpose + privateKey, _ := crypto.GenerateKey() + peer := validator.New(getPublicKeyAddress(privateKey)) + valSet := validator.NewSet([]common.Address{peer.Address()}, istanbul.RoundRobin) + + sys := NewTestSystemWithBackend(uint64(1), uint64(0)) + + testCases := []struct { + expected error + + prepare *istanbul.Subject + roundState *roundState + }{ + { + // normal case + expected: nil, + prepare: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + valSet, + ), + }, + { + // old message + expected: errInconsistentSubject, + prepare: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, + valSet, + ), + }, + { + // different digest + expected: errInconsistentSubject, + prepare: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + Digest: common.StringToHash("1234567890"), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, + valSet, + ), + }, + { + // malicious package(lack of sequence) + expected: errInconsistentSubject, + prepare: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: nil}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(1)}, + valSet, + ), + }, + { + // wrong PREPARE message with same sequence but different round + expected: errInconsistentSubject, + prepare: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(1), Sequence: big.NewInt(0)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + valSet, + ), + }, + { + // wrong PREPARE message with same round but different sequence + expected: errInconsistentSubject, + prepare: &istanbul.Subject{ + View: &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(1)}, + Digest: newTestProposal().Hash(), + }, + roundState: newTestRoundState( + &istanbul.View{Round: big.NewInt(0), Sequence: big.NewInt(0)}, + valSet, + ), + }, + } + for i, test := range testCases { + c := sys.backends[0].engine.(*core) + c.current = test.roundState + + if err := c.verifyPrepare(test.prepare, peer); err != nil { + if err != test.expected { + t.Errorf("result %d: error mismatch: have %v, want %v", i, err, test.expected) + } + } + } +} diff --git a/consensus/istanbul/core/preprepare.go b/consensus/istanbul/core/preprepare.go new file mode 100644 index 0000000000..dc789206ed --- /dev/null +++ b/consensus/istanbul/core/preprepare.go @@ -0,0 +1,129 @@ +// Copyright 2017 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 . + +package core + +import ( + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func (c *core) sendPreprepare(request *istanbul.Request) { + logger := c.logger.New("state", c.state) + // If I'm the proposer and I have the same sequence with the proposal + if c.current.Sequence().Cmp(request.Proposal.Number()) == 0 && c.IsProposer() { + curView := c.currentView() + preprepare, err := Encode(&istanbul.Preprepare{ + View: curView, + Proposal: request.Proposal, + }) + if err != nil { + logger.Error("Failed to encode", "view", curView) + return + } + c.broadcast(&message{ + Code: msgPreprepare, + Msg: preprepare, + }) + } +} + +func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { + logger := c.logger.New("from", src, "state", c.state) + + // Decode PRE-PREPARE + var preprepare *istanbul.Preprepare + err := msg.Decode(&preprepare) + if err != nil { + return errFailedDecodePreprepare + } + + // Ensure we have the same view with the PRE-PREPARE message + // If it is old message, see if we need to broadcast COMMIT + if err := c.checkMessage(msgPreprepare, preprepare.View); err != nil { + if err == errOldMessage { + // Get validator set for the given proposal + valSet := c.backend.ParentValidators(preprepare.Proposal).Copy() + previousProposer := c.backend.GetProposer(preprepare.Proposal.Number().Uint64() - 1) + valSet.CalcProposer(previousProposer, preprepare.View.Round.Uint64()) + // Broadcast COMMIT if it is an existing block + // 1. The proposer needs to be a proposer matches the given (Sequence + Round) + // 2. The given block must exist + if valSet.IsProposer(src.Address()) && c.backend.HasPropsal(preprepare.Proposal.Hash(), preprepare.Proposal.Number()) { + c.sendCommitForOldBlock(preprepare.View, preprepare.Proposal.Hash()) + return nil + } + } + return err + } + + // Check if the message comes from current proposer + if !c.valSet.IsProposer(src.Address()) { + logger.Warn("Ignore preprepare messages from non-proposer") + return errNotFromProposer + } + + // Verify the proposal we received + if duration, err := c.backend.Verify(preprepare.Proposal); err != nil { + // if it's a future block, we will handle it again after the duration + if err == consensus.ErrFutureBlock { + logger.Info("Proposed block will be handled in the future", "err", err, "duration", duration) + c.stopFuturePreprepareTimer() + c.futurePreprepareTimer = time.AfterFunc(duration, func() { + c.sendEvent(backlogEvent{ + src: src, + msg: msg, + }) + }) + } else { + logger.Warn("Failed to verify proposal", "err", err, "duration", duration) + c.sendNextRoundChange() + } + return err + } + + // Here is about to accept the PRE-PREPARE + if c.state == StateAcceptRequest { + // Send ROUND CHANGE if the locked proposal and the received proposal are different + if c.current.IsHashLocked() { + if preprepare.Proposal.Hash() == c.current.GetLockedHash() { + // Broadcast COMMIT and enters Prepared state directly + c.acceptPreprepare(preprepare) + c.setState(StatePrepared) + c.sendCommit() + } else { + // Send round change + c.sendNextRoundChange() + } + } else { + // Either + // 1. the locked proposal and the received proposal match + // 2. we have no locked proposal + c.acceptPreprepare(preprepare) + c.setState(StatePreprepared) + c.sendPrepare() + } + } + + return nil +} + +func (c *core) acceptPreprepare(preprepare *istanbul.Preprepare) { + c.consensusTimestamp = time.Now() + c.current.SetPreprepare(preprepare) +} diff --git a/consensus/istanbul/core/preprepare_test.go b/consensus/istanbul/core/preprepare_test.go new file mode 100644 index 0000000000..1097cfeba9 --- /dev/null +++ b/consensus/istanbul/core/preprepare_test.go @@ -0,0 +1,298 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func newTestPreprepare(v *istanbul.View) *istanbul.Preprepare { + return &istanbul.Preprepare{ + View: v, + Proposal: newTestProposal(), + } +} + +func TestHandlePreprepare(t *testing.T) { + N := uint64(4) // replica 0 is the proposer, it will send messages to others + F := uint64(1) // F does not affect tests + + testCases := []struct { + system *testSystem + expectedRequest istanbul.Proposal + expectedErr error + existingBlock bool + }{ + { + // normal case + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i != 0 { + c.state = StateAcceptRequest + } + } + return sys + }(), + newTestProposal(), + nil, + false, + }, + { + // future message + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i != 0 { + c.state = StateAcceptRequest + // hack: force set subject that future message can be simulated + c.current = newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(0), + }, + c.valSet, + ) + + } else { + c.current.SetSequence(big.NewInt(10)) + } + } + return sys + }(), + makeBlock(1), + errFutureMessage, + false, + }, + { + // non-proposer + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + // force remove replica 0, let replica 1 be the proposer + sys.backends = sys.backends[1:] + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i != 0 { + // replica 0 is the proposer + c.state = StatePreprepared + } + } + return sys + }(), + makeBlock(1), + errNotFromProposer, + false, + }, + { + // errOldMessage + func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i != 0 { + c.state = StatePreprepared + c.current.SetSequence(big.NewInt(10)) + c.current.SetRound(big.NewInt(10)) + } + } + return sys + }(), + makeBlock(1), + errOldMessage, + false, + }, + } + +OUTER: + for _, test := range testCases { + test.system.Run(false) + + v0 := test.system.backends[0] + r0 := v0.engine.(*core) + + curView := r0.currentView() + + preprepare := &istanbul.Preprepare{ + View: curView, + Proposal: test.expectedRequest, + } + + for i, v := range test.system.backends { + // i == 0 is primary backend, it is responsible for send PRE-PREPARE messages to others. + if i == 0 { + continue + } + + c := v.engine.(*core) + + m, _ := Encode(preprepare) + _, val := r0.valSet.GetByAddress(v0.Address()) + // run each backends and verify handlePreprepare function. + if err := c.handlePreprepare(&message{ + Code: msgPreprepare, + Msg: m, + Address: v0.Address(), + }, val); err != nil { + if err != test.expectedErr { + t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) + } + continue OUTER + } + + if c.state != StatePreprepared { + t.Errorf("state mismatch: have %v, want %v", c.state, StatePreprepared) + } + + if !test.existingBlock && !reflect.DeepEqual(c.current.Subject().View, curView) { + t.Errorf("view mismatch: have %v, want %v", c.current.Subject().View, curView) + } + + // verify prepare messages + decodedMsg := new(message) + err := decodedMsg.FromPayload(v.sentMsgs[0], nil) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + expectedCode := msgPrepare + if test.existingBlock { + expectedCode = msgCommit + } + if decodedMsg.Code != expectedCode { + t.Errorf("message code mismatch: have %v, want %v", decodedMsg.Code, expectedCode) + } + + var subject *istanbul.Subject + err = decodedMsg.Decode(&subject) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + if !test.existingBlock && !reflect.DeepEqual(subject, c.current.Subject()) { + t.Errorf("subject mismatch: have %v, want %v", subject, c.current.Subject()) + } + + } + } +} + +func TestHandlePreprepareWithLock(t *testing.T) { + N := uint64(4) // replica 0 is the proposer, it will send messages to others + F := uint64(1) // F does not affect tests + proposal := newTestProposal() + mismatchProposal := makeBlock(10) + newSystem := func() *testSystem { + sys := NewTestSystemWithBackend(N, F) + + for i, backend := range sys.backends { + c := backend.engine.(*core) + c.valSet = backend.peers + if i != 0 { + c.state = StateAcceptRequest + } + c.roundChangeSet = newRoundChangeSet(c.valSet) + } + return sys + } + + testCases := []struct { + system *testSystem + proposal istanbul.Proposal + lockProposal istanbul.Proposal + }{ + { + newSystem(), + proposal, + proposal, + }, + { + newSystem(), + proposal, + mismatchProposal, + }, + } + + for _, test := range testCases { + test.system.Run(false) + v0 := test.system.backends[0] + r0 := v0.engine.(*core) + curView := r0.currentView() + preprepare := &istanbul.Preprepare{ + View: curView, + Proposal: test.proposal, + } + lockPreprepare := &istanbul.Preprepare{ + View: curView, + Proposal: test.lockProposal, + } + + for i, v := range test.system.backends { + // i == 0 is primary backend, it is responsible for send PRE-PREPARE messages to others. + if i == 0 { + continue + } + + c := v.engine.(*core) + c.current.SetPreprepare(lockPreprepare) + c.current.LockHash() + m, _ := Encode(preprepare) + _, val := r0.valSet.GetByAddress(v0.Address()) + if err := c.handlePreprepare(&message{ + Code: msgPreprepare, + Msg: m, + Address: v0.Address(), + }, val); err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + if test.proposal == test.lockProposal { + if c.state != StatePrepared { + t.Errorf("state mismatch: have %v, want %v", c.state, StatePreprepared) + } + if !reflect.DeepEqual(curView, c.currentView()) { + t.Errorf("view mismatch: have %v, want %v", c.currentView(), curView) + } + } else { + // Should stay at StateAcceptRequest + if c.state != StateAcceptRequest { + t.Errorf("state mismatch: have %v, want %v", c.state, StateAcceptRequest) + } + // Should have triggered a round change + expectedView := &istanbul.View{ + Sequence: curView.Sequence, + Round: big.NewInt(1), + } + if !reflect.DeepEqual(expectedView, c.currentView()) { + t.Errorf("view mismatch: have %v, want %v", c.currentView(), expectedView) + } + } + } + } +} diff --git a/consensus/istanbul/core/request.go b/consensus/istanbul/core/request.go new file mode 100644 index 0000000000..7b73c5ff2a --- /dev/null +++ b/consensus/istanbul/core/request.go @@ -0,0 +1,99 @@ +// Copyright 2017 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 . + +package core + +import ( + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func (c *core) handleRequest(request *istanbul.Request) error { + logger := c.logger.New("state", c.state, "seq", c.current.sequence) + if err := c.checkRequestMsg(request); err != nil { + if err == errInvalidMessage { + logger.Warn("invalid request") + return err + } + logger.Warn("unexpected request", "err", err, "number", request.Proposal.Number(), "hash", request.Proposal.Hash()) + return err + } + logger.Trace("handleRequest", "number", request.Proposal.Number(), "hash", request.Proposal.Hash()) + + c.current.pendingRequest = request + if c.state == StateAcceptRequest { + c.sendPreprepare(request) + } + return nil +} + +// check request state +// return errInvalidMessage if the message is invalid +// return errFutureMessage if the sequence of proposal is larger than current sequence +// return errOldMessage if the sequence of proposal is smaller than current sequence +func (c *core) checkRequestMsg(request *istanbul.Request) error { + if request == nil || request.Proposal == nil { + return errInvalidMessage + } + + if c := c.current.sequence.Cmp(request.Proposal.Number()); c > 0 { + return errOldMessage + } else if c < 0 { + return errFutureMessage + } else { + return nil + } +} + +func (c *core) storeRequestMsg(request *istanbul.Request) { + logger := c.logger.New("state", c.state) + + logger.Trace("Store future request", "number", request.Proposal.Number(), "hash", request.Proposal.Hash()) + + c.pendingRequestsMu.Lock() + defer c.pendingRequestsMu.Unlock() + + c.pendingRequests.Push(request, float32(-request.Proposal.Number().Int64())) +} + +func (c *core) processPendingRequests() { + c.pendingRequestsMu.Lock() + defer c.pendingRequestsMu.Unlock() + + for !(c.pendingRequests.Empty()) { + m, prio := c.pendingRequests.Pop() + r, ok := m.(*istanbul.Request) + if !ok { + c.logger.Warn("Malformed request, skip", "msg", m) + continue + } + // Push back if it's a future message + err := c.checkRequestMsg(r) + if err != nil { + if err == errFutureMessage { + c.logger.Trace("Stop processing request", "number", r.Proposal.Number(), "hash", r.Proposal.Hash()) + c.pendingRequests.Push(m, prio) + break + } + c.logger.Trace("Skip the pending request", "number", r.Proposal.Number(), "hash", r.Proposal.Hash(), "err", err) + continue + } + c.logger.Trace("Post pending request", "number", r.Proposal.Number(), "hash", r.Proposal.Hash()) + + go c.sendEvent(istanbul.RequestEvent{ + Proposal: r.Proposal, + }) + } +} diff --git a/consensus/istanbul/core/request_test.go b/consensus/istanbul/core/request_test.go new file mode 100644 index 0000000000..5ee179c1c8 --- /dev/null +++ b/consensus/istanbul/core/request_test.go @@ -0,0 +1,138 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "reflect" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/karalabe/cookiejar.v2/collections/prque" +) + +func TestCheckRequestMsg(t *testing.T) { + c := &core{ + state: StateAcceptRequest, + current: newRoundState(&istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(0), + }, newTestValidatorSet(4), common.Hash{}, nil, nil, nil), + } + + // invalid request + err := c.checkRequestMsg(nil) + if err != errInvalidMessage { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidMessage) + } + r := &istanbul.Request{ + Proposal: nil, + } + err = c.checkRequestMsg(r) + if err != errInvalidMessage { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidMessage) + } + + // old request + r = &istanbul.Request{ + Proposal: makeBlock(0), + } + err = c.checkRequestMsg(r) + if err != errOldMessage { + t.Errorf("error mismatch: have %v, want %v", err, errOldMessage) + } + + // future request + r = &istanbul.Request{ + Proposal: makeBlock(2), + } + err = c.checkRequestMsg(r) + if err != errFutureMessage { + t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage) + } + + // current request + r = &istanbul.Request{ + Proposal: makeBlock(1), + } + err = c.checkRequestMsg(r) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } +} + +func TestStoreRequestMsg(t *testing.T) { + backend := &testSystemBackend{ + events: new(event.TypeMux), + } + c := &core{ + logger: log.New("backend", "test", "id", 0), + backend: backend, + state: StateAcceptRequest, + current: newRoundState(&istanbul.View{ + Sequence: big.NewInt(0), + Round: big.NewInt(0), + }, newTestValidatorSet(4), common.Hash{}, nil, nil, nil), + pendingRequests: prque.New(), + pendingRequestsMu: new(sync.Mutex), + } + requests := []istanbul.Request{ + { + Proposal: makeBlock(1), + }, + { + Proposal: makeBlock(2), + }, + { + Proposal: makeBlock(3), + }, + } + + c.storeRequestMsg(&requests[1]) + c.storeRequestMsg(&requests[0]) + c.storeRequestMsg(&requests[2]) + if c.pendingRequests.Size() != len(requests) { + t.Errorf("the size of pending requests mismatch: have %v, want %v", c.pendingRequests.Size(), len(requests)) + } + + c.current.sequence = big.NewInt(3) + + c.subscribeEvents() + defer c.unsubscribeEvents() + + c.processPendingRequests() + + const timeoutDura = 2 * time.Second + timeout := time.NewTimer(timeoutDura) + select { + case ev := <-c.events.Chan(): + e, ok := ev.Data.(istanbul.RequestEvent) + if !ok { + t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) + } + if e.Proposal.Number().Cmp(requests[2].Proposal.Number()) != 0 { + t.Errorf("the number of proposal mismatch: have %v, want %v", e.Proposal.Number(), requests[2].Proposal.Number()) + } + case <-timeout.C: + t.Error("unexpected timeout occurs") + } +} diff --git a/consensus/istanbul/core/roundchange.go b/consensus/istanbul/core/roundchange.go new file mode 100644 index 0000000000..6c02f626ec --- /dev/null +++ b/consensus/istanbul/core/roundchange.go @@ -0,0 +1,172 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +// sendNextRoundChange sends the ROUND CHANGE message with current round + 1 +func (c *core) sendNextRoundChange() { + cv := c.currentView() + c.sendRoundChange(new(big.Int).Add(cv.Round, common.Big1)) +} + +// sendRoundChange sends the ROUND CHANGE message with the given round +func (c *core) sendRoundChange(round *big.Int) { + logger := c.logger.New("state", c.state) + + cv := c.currentView() + if cv.Round.Cmp(round) >= 0 { + logger.Error("Cannot send out the round change", "current round", cv.Round, "target round", round) + return + } + + c.catchUpRound(&istanbul.View{ + // The round number we'd like to transfer to. + Round: new(big.Int).Set(round), + Sequence: new(big.Int).Set(cv.Sequence), + }) + + // Now we have the new round number and sequence number + cv = c.currentView() + rc := &istanbul.Subject{ + View: cv, + Digest: common.Hash{}, + } + + payload, err := Encode(rc) + if err != nil { + logger.Error("Failed to encode ROUND CHANGE", "rc", rc, "err", err) + return + } + + c.broadcast(&message{ + Code: msgRoundChange, + Msg: payload, + }) +} + +func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error { + logger := c.logger.New("state", c.state, "from", src.Address().Hex()) + + // Decode ROUND CHANGE message + var rc *istanbul.Subject + if err := msg.Decode(&rc); err != nil { + logger.Error("Failed to decode ROUND CHANGE", "err", err) + return errInvalidMessage + } + + if err := c.checkMessage(msgRoundChange, rc.View); err != nil { + return err + } + + cv := c.currentView() + roundView := rc.View + + // Add the ROUND CHANGE message to its message set and return how many + // messages we've got with the same round number and sequence number. + num, err := c.roundChangeSet.Add(roundView.Round, msg) + if err != nil { + logger.Warn("Failed to add round change message", "from", src, "msg", msg, "err", err) + return err + } + + // Once we received f+1 ROUND CHANGE messages, those messages form a weak certificate. + // If our round number is smaller than the certificate's round number, we would + // try to catch up the round number. + if c.waitingForRoundChange && num == c.valSet.F()+1 { + if cv.Round.Cmp(roundView.Round) < 0 { + c.sendRoundChange(roundView.Round) + } + return nil + } else if num == c.QuorumSize() && (c.waitingForRoundChange || cv.Round.Cmp(roundView.Round) < 0) { + // We've received 2f+1/Ceil(2N/3) ROUND CHANGE messages, start a new round immediately. + c.startNewRound(roundView.Round) + return nil + } else if cv.Round.Cmp(roundView.Round) < 0 { + // Only gossip the message with current round to other validators. + return errIgnored + } + return nil +} + +// ---------------------------------------------------------------------------- + +func newRoundChangeSet(valSet istanbul.ValidatorSet) *roundChangeSet { + return &roundChangeSet{ + validatorSet: valSet, + roundChanges: make(map[uint64]*messageSet), + mu: new(sync.Mutex), + } +} + +type roundChangeSet struct { + validatorSet istanbul.ValidatorSet + roundChanges map[uint64]*messageSet + mu *sync.Mutex +} + +// Add adds the round and message into round change set +func (rcs *roundChangeSet) Add(r *big.Int, msg *message) (int, error) { + rcs.mu.Lock() + defer rcs.mu.Unlock() + + round := r.Uint64() + if rcs.roundChanges[round] == nil { + rcs.roundChanges[round] = newMessageSet(rcs.validatorSet) + } + err := rcs.roundChanges[round].Add(msg) + if err != nil { + return 0, err + } + return rcs.roundChanges[round].Size(), nil +} + +// Clear deletes the messages with smaller round +func (rcs *roundChangeSet) Clear(round *big.Int) { + rcs.mu.Lock() + defer rcs.mu.Unlock() + + for k, rms := range rcs.roundChanges { + if len(rms.Values()) == 0 || k < round.Uint64() { + delete(rcs.roundChanges, k) + } + } +} + +// MaxRound returns the max round which the number of messages is equal or larger than num +func (rcs *roundChangeSet) MaxRound(num int) *big.Int { + rcs.mu.Lock() + defer rcs.mu.Unlock() + + var maxRound *big.Int + for k, rms := range rcs.roundChanges { + if rms.Size() < num { + continue + } + r := big.NewInt(int64(k)) + if maxRound == nil || maxRound.Cmp(r) < 0 { + maxRound = r + } + } + return maxRound +} diff --git a/consensus/istanbul/core/roundchange_test.go b/consensus/istanbul/core/roundchange_test.go new file mode 100644 index 0000000000..835219ae81 --- /dev/null +++ b/consensus/istanbul/core/roundchange_test.go @@ -0,0 +1,92 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" +) + +func TestRoundChangeSet(t *testing.T) { + vset := validator.NewSet(generateValidators(4), istanbul.RoundRobin) + rc := newRoundChangeSet(vset) + + view := &istanbul.View{ + Sequence: big.NewInt(1), + Round: big.NewInt(1), + } + r := &istanbul.Subject{ + View: view, + Digest: common.Hash{}, + } + m, _ := Encode(r) + + // Test Add() + // Add message from all validators + for i, v := range vset.List() { + msg := &message{ + Code: msgRoundChange, + Msg: m, + Address: v.Address(), + } + rc.Add(view.Round, msg) + if rc.roundChanges[view.Round.Uint64()].Size() != i+1 { + t.Errorf("the size of round change messages mismatch: have %v, want %v", rc.roundChanges[view.Round.Uint64()].Size(), i+1) + } + } + + // Add message again from all validators, but the size should be the same + for _, v := range vset.List() { + msg := &message{ + Code: msgRoundChange, + Msg: m, + Address: v.Address(), + } + rc.Add(view.Round, msg) + if rc.roundChanges[view.Round.Uint64()].Size() != vset.Size() { + t.Errorf("the size of round change messages mismatch: have %v, want %v", rc.roundChanges[view.Round.Uint64()].Size(), vset.Size()) + } + } + + // Test MaxRound() + for i := 0; i < 10; i++ { + maxRound := rc.MaxRound(i) + if i <= vset.Size() { + if maxRound == nil || maxRound.Cmp(view.Round) != 0 { + t.Errorf("max round mismatch: have %v, want %v", maxRound, view.Round) + } + } else if maxRound != nil { + t.Errorf("max round mismatch: have %v, want nil", maxRound) + } + } + + // Test Clear() + for i := int64(0); i < 2; i++ { + rc.Clear(big.NewInt(i)) + if rc.roundChanges[view.Round.Uint64()].Size() != vset.Size() { + t.Errorf("the size of round change messages mismatch: have %v, want %v", rc.roundChanges[view.Round.Uint64()].Size(), vset.Size()) + } + } + rc.Clear(big.NewInt(2)) + if rc.roundChanges[view.Round.Uint64()] != nil { + t.Errorf("the change messages mismatch: have %v, want nil", rc.roundChanges[view.Round.Uint64()]) + } +} diff --git a/consensus/istanbul/core/roundstate.go b/consensus/istanbul/core/roundstate.go new file mode 100644 index 0000000000..8f011bfead --- /dev/null +++ b/consensus/istanbul/core/roundstate.go @@ -0,0 +1,221 @@ +// Copyright 2017 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 . + +package core + +import ( + "io" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/rlp" +) + +// newRoundState creates a new roundState instance with the given view and validatorSet +// lockedHash and preprepare are for round change when lock exists, +// we need to keep a reference of preprepare in order to propose locked proposal when there is a lock and itself is the proposer +func newRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, lockedHash common.Hash, preprepare *istanbul.Preprepare, pendingRequest *istanbul.Request, hasBadProposal func(hash common.Hash) bool) *roundState { + return &roundState{ + round: view.Round, + sequence: view.Sequence, + Preprepare: preprepare, + Prepares: newMessageSet(validatorSet), + Commits: newMessageSet(validatorSet), + lockedHash: lockedHash, + mu: new(sync.RWMutex), + pendingRequest: pendingRequest, + hasBadProposal: hasBadProposal, + } +} + +// roundState stores the consensus state +type roundState struct { + round *big.Int + sequence *big.Int + Preprepare *istanbul.Preprepare + Prepares *messageSet + Commits *messageSet + lockedHash common.Hash + pendingRequest *istanbul.Request + + mu *sync.RWMutex + hasBadProposal func(hash common.Hash) bool +} + +func (s *roundState) GetPrepareOrCommitSize() int { + s.mu.RLock() + defer s.mu.RUnlock() + + result := s.Prepares.Size() + s.Commits.Size() + + // find duplicate one + for _, m := range s.Prepares.Values() { + if s.Commits.Get(m.Address) != nil { + result-- + } + } + return result +} + +func (s *roundState) Subject() *istanbul.Subject { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.Preprepare == nil { + return nil + } + + return &istanbul.Subject{ + View: &istanbul.View{ + Round: new(big.Int).Set(s.round), + Sequence: new(big.Int).Set(s.sequence), + }, + Digest: s.Preprepare.Proposal.Hash(), + } +} + +func (s *roundState) SetPreprepare(preprepare *istanbul.Preprepare) { + s.mu.Lock() + defer s.mu.Unlock() + + s.Preprepare = preprepare +} + +func (s *roundState) Proposal() istanbul.Proposal { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.Preprepare != nil { + return s.Preprepare.Proposal + } + + return nil +} + +func (s *roundState) SetRound(r *big.Int) { + s.mu.Lock() + defer s.mu.Unlock() + + s.round = new(big.Int).Set(r) +} + +func (s *roundState) Round() *big.Int { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.round +} + +func (s *roundState) SetSequence(seq *big.Int) { + s.mu.Lock() + defer s.mu.Unlock() + + s.sequence = seq +} + +func (s *roundState) Sequence() *big.Int { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.sequence +} + +func (s *roundState) LockHash() { + s.mu.Lock() + defer s.mu.Unlock() + + if s.Preprepare != nil { + s.lockedHash = s.Preprepare.Proposal.Hash() + } +} + +func (s *roundState) UnlockHash() { + s.mu.Lock() + defer s.mu.Unlock() + + s.lockedHash = common.Hash{} +} + +func (s *roundState) IsHashLocked() bool { + s.mu.RLock() + defer s.mu.RUnlock() + + if common.EmptyHash(s.lockedHash) { + return false + } + return !s.hasBadProposal(s.GetLockedHash()) +} + +func (s *roundState) GetLockedHash() common.Hash { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.lockedHash +} + +// The DecodeRLP method should read one value from the given +// Stream. It is not forbidden to read less or more, but it might +// be confusing. +func (s *roundState) DecodeRLP(stream *rlp.Stream) error { + var ss struct { + Round *big.Int + Sequence *big.Int + Preprepare *istanbul.Preprepare + Prepares *messageSet + Commits *messageSet + lockedHash common.Hash + pendingRequest *istanbul.Request + } + + if err := stream.Decode(&ss); err != nil { + return err + } + s.round = ss.Round + s.sequence = ss.Sequence + s.Preprepare = ss.Preprepare + s.Prepares = ss.Prepares + s.Commits = ss.Commits + s.lockedHash = ss.lockedHash + s.pendingRequest = ss.pendingRequest + s.mu = new(sync.RWMutex) + + return nil +} + +// EncodeRLP should write the RLP encoding of its receiver to w. +// If the implementation is a pointer method, it may also be +// called for nil pointers. +// +// Implementations should generate valid RLP. The data written is +// not verified at the moment, but a future version might. It is +// recommended to write only a single value but writing multiple +// values or no value at all is also permitted. +func (s *roundState) EncodeRLP(w io.Writer) error { + s.mu.RLock() + defer s.mu.RUnlock() + + return rlp.Encode(w, []interface{}{ + s.round, + s.sequence, + s.Preprepare, + s.Prepares, + s.Commits, + s.lockedHash, + s.pendingRequest, + }) +} diff --git a/consensus/istanbul/core/roundstate_test.go b/consensus/istanbul/core/roundstate_test.go new file mode 100644 index 0000000000..7cf1979c76 --- /dev/null +++ b/consensus/istanbul/core/roundstate_test.go @@ -0,0 +1,76 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func newTestRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet) *roundState { + return &roundState{ + round: view.Round, + sequence: view.Sequence, + Preprepare: newTestPreprepare(view), + Prepares: newMessageSet(validatorSet), + Commits: newMessageSet(validatorSet), + mu: new(sync.RWMutex), + hasBadProposal: func(hash common.Hash) bool { + return false + }, + } +} + +func TestLockHash(t *testing.T) { + sys := NewTestSystemWithBackend(1, 0) + rs := newTestRoundState( + &istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(0), + }, + sys.backends[0].peers, + ) + if !common.EmptyHash(rs.GetLockedHash()) { + t.Errorf("error mismatch: have %v, want empty", rs.GetLockedHash()) + } + if rs.IsHashLocked() { + t.Error("IsHashLocked should return false") + } + + // Lock + expected := rs.Proposal().Hash() + rs.LockHash() + if expected != rs.GetLockedHash() { + t.Errorf("error mismatch: have %v, want %v", rs.GetLockedHash(), expected) + } + if !rs.IsHashLocked() { + t.Error("IsHashLocked should return true") + } + + // Unlock + rs.UnlockHash() + if !common.EmptyHash(rs.GetLockedHash()) { + t.Errorf("error mismatch: have %v, want empty", rs.GetLockedHash()) + } + if rs.IsHashLocked() { + t.Error("IsHashLocked should return false") + } +} diff --git a/consensus/istanbul/core/testbackend_test.go b/consensus/istanbul/core/testbackend_test.go new file mode 100644 index 0000000000..4fe5aaa68d --- /dev/null +++ b/consensus/istanbul/core/testbackend_test.go @@ -0,0 +1,291 @@ +// Copyright 2017 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 . + +package core + +import ( + "crypto/ecdsa" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + elog "github.com/ethereum/go-ethereum/log" +) + +var testLogger = elog.New() + +type testSystemBackend struct { + id uint64 + sys *testSystem + + engine Engine + peers istanbul.ValidatorSet + events *event.TypeMux + + committedMsgs []testCommittedMsgs + sentMsgs [][]byte // store the message when Send is called by core + + address common.Address + db ethdb.Database +} + +type testCommittedMsgs struct { + commitProposal istanbul.Proposal + committedSeals [][]byte +} + +// ============================================== +// +// define the functions that needs to be provided for Istanbul. + +func (self *testSystemBackend) Address() common.Address { + return self.address +} + +// Peers returns all connected peers +func (self *testSystemBackend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet { + return self.peers +} + +func (self *testSystemBackend) EventMux() *event.TypeMux { + return self.events +} + +func (self *testSystemBackend) Send(message []byte, target common.Address) error { + testLogger.Info("enqueuing a message...", "address", self.Address()) + self.sentMsgs = append(self.sentMsgs, message) + self.sys.queuedMessage <- istanbul.MessageEvent{ + Payload: message, + } + return nil +} + +func (self *testSystemBackend) Broadcast(valSet istanbul.ValidatorSet, message []byte) error { + testLogger.Info("enqueuing a message...", "address", self.Address()) + self.sentMsgs = append(self.sentMsgs, message) + self.sys.queuedMessage <- istanbul.MessageEvent{ + Payload: message, + } + return nil +} + +func (self *testSystemBackend) Gossip(valSet istanbul.ValidatorSet, message []byte) error { + testLogger.Warn("not sign any data") + return nil +} + +func (self *testSystemBackend) Commit(proposal istanbul.Proposal, seals [][]byte) error { + testLogger.Info("commit message", "address", self.Address()) + self.committedMsgs = append(self.committedMsgs, testCommittedMsgs{ + commitProposal: proposal, + committedSeals: seals, + }) + + // fake new head events + go self.events.Post(istanbul.FinalCommittedEvent{}) + return nil +} + +func (self *testSystemBackend) Verify(proposal istanbul.Proposal) (time.Duration, error) { + return 0, nil +} + +func (self *testSystemBackend) Sign(data []byte) ([]byte, error) { + testLogger.Info("returning current backend address so that CheckValidatorSignature returns the same value") + return self.address.Bytes(), nil +} + +func (self *testSystemBackend) CheckSignature([]byte, common.Address, []byte) error { + return nil +} + +func (self *testSystemBackend) CheckValidatorSignature(data []byte, sig []byte) (common.Address, error) { + return common.BytesToAddress(sig), nil +} + +func (self *testSystemBackend) Hash(b interface{}) common.Hash { + return common.StringToHash("Test") +} + +func (self *testSystemBackend) NewRequest(request istanbul.Proposal) { + go self.events.Post(istanbul.RequestEvent{ + Proposal: request, + }) +} + +func (self *testSystemBackend) HasBadProposal(hash common.Hash) bool { + return false +} + +func (self *testSystemBackend) LastProposal() (istanbul.Proposal, common.Address) { + l := len(self.committedMsgs) + if l > 0 { + return self.committedMsgs[l-1].commitProposal, common.Address{} + } + return makeBlock(0), common.Address{} +} + +// Only block height 5 will return true +func (self *testSystemBackend) HasPropsal(hash common.Hash, number *big.Int) bool { + return number.Cmp(big.NewInt(5)) == 0 +} + +func (self *testSystemBackend) GetProposer(number uint64) common.Address { + return common.Address{} +} + +func (self *testSystemBackend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet { + return self.peers +} + +func (sb *testSystemBackend) Close() error { + return nil +} + +// ============================================== +// +// define the struct that need to be provided for integration tests. + +type testSystem struct { + backends []*testSystemBackend + + queuedMessage chan istanbul.MessageEvent + quit chan struct{} +} + +func newTestSystem(n uint64) *testSystem { + testLogger.SetHandler(elog.StdoutHandler) + return &testSystem{ + backends: make([]*testSystemBackend, n), + + queuedMessage: make(chan istanbul.MessageEvent), + quit: make(chan struct{}), + } +} + +func generateValidators(n int) []common.Address { + vals := make([]common.Address, 0) + for i := 0; i < n; i++ { + privateKey, _ := crypto.GenerateKey() + vals = append(vals, crypto.PubkeyToAddress(privateKey.PublicKey)) + } + return vals +} + +func newTestValidatorSet(n int) istanbul.ValidatorSet { + return validator.NewSet(generateValidators(n), istanbul.RoundRobin) +} + +// FIXME: int64 is needed for N and F +func NewTestSystemWithBackend(n, f uint64) *testSystem { + testLogger.SetHandler(elog.StdoutHandler) + + addrs := generateValidators(int(n)) + sys := newTestSystem(n) + config := istanbul.DefaultConfig + + for i := uint64(0); i < n; i++ { + vset := validator.NewSet(addrs, istanbul.RoundRobin) + backend := sys.NewBackend(i) + backend.peers = vset + backend.address = vset.GetByIndex(i).Address() + + core := New(backend, config).(*core) + core.state = StateAcceptRequest + core.current = newRoundState(&istanbul.View{ + Round: big.NewInt(0), + Sequence: big.NewInt(1), + }, vset, common.Hash{}, nil, nil, func(hash common.Hash) bool { + return false + }) + core.valSet = vset + core.logger = testLogger + core.validateFn = backend.CheckValidatorSignature + + backend.engine = core + } + + return sys +} + +// listen will consume messages from queue and deliver a message to core +func (t *testSystem) listen() { + for { + select { + case <-t.quit: + return + case queuedMessage := <-t.queuedMessage: + testLogger.Info("consuming a queue message...") + for _, backend := range t.backends { + go backend.EventMux().Post(queuedMessage) + } + } + } +} + +// Run will start system components based on given flag, and returns a closer +// function that caller can control lifecycle +// +// Given a true for core if you want to initialize core engine. +func (t *testSystem) Run(core bool) func() { + for _, b := range t.backends { + if core { + b.engine.Start() // start Istanbul core + } + } + + go t.listen() + closer := func() { t.stop(core) } + return closer +} + +func (t *testSystem) stop(core bool) { + close(t.quit) + + for _, b := range t.backends { + if core { + b.engine.Stop() + } + } +} + +func (t *testSystem) NewBackend(id uint64) *testSystemBackend { + // assume always success + ethDB := rawdb.NewMemoryDatabase() + backend := &testSystemBackend{ + id: id, + sys: t, + events: new(event.TypeMux), + db: ethDB, + } + + t.backends[id] = backend + return backend +} + +// ============================================== +// +// helper functions. + +func getPublicKeyAddress(privateKey *ecdsa.PrivateKey) common.Address { + return crypto.PubkeyToAddress(privateKey.PublicKey) +} diff --git a/consensus/istanbul/core/types.go b/consensus/istanbul/core/types.go new file mode 100644 index 0000000000..f8e1c81287 --- /dev/null +++ b/consensus/istanbul/core/types.go @@ -0,0 +1,180 @@ +// Copyright 2017 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 . + +package core + +import ( + "bytes" + "fmt" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +type Engine interface { + Start() error + Stop() error + + IsProposer() bool + + // verify if a hash is the same as the proposed block in the current pending request + // + // this is useful when the engine is currently the proposer + // + // pending request is populated right at the preprepare stage so this would give us the earliest verification + // to avoid any race condition of coming propagated blocks + IsCurrentProposal(blockHash common.Hash) bool +} + +type State uint64 + +const ( + StateAcceptRequest State = iota + StatePreprepared + StatePrepared + StateCommitted +) + +func (s State) String() string { + if s == StateAcceptRequest { + return "Accept request" + } else if s == StatePreprepared { + return "Preprepared" + } else if s == StatePrepared { + return "Prepared" + } else if s == StateCommitted { + return "Committed" + } else { + return "Unknown" + } +} + +// Cmp compares s and y and returns: +// -1 if s is the previous state of y +// 0 if s and y are the same state +// +1 if s is the next state of y +func (s State) Cmp(y State) int { + if uint64(s) < uint64(y) { + return -1 + } + if uint64(s) > uint64(y) { + return 1 + } + return 0 +} + +const ( + msgPreprepare uint64 = iota + msgPrepare + msgCommit + msgRoundChange + // msgAll +) + +type message struct { + Code uint64 + Msg []byte + Address common.Address + Signature []byte + CommittedSeal []byte +} + +// ============================================== +// +// define the functions that needs to be provided for rlp Encoder/Decoder. + +// EncodeRLP serializes m into the Ethereum RLP format. +func (m *message) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{m.Code, m.Msg, m.Address, m.Signature, m.CommittedSeal}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (m *message) DecodeRLP(s *rlp.Stream) error { + var msg struct { + Code uint64 + Msg []byte + Address common.Address + Signature []byte + CommittedSeal []byte + } + + if err := s.Decode(&msg); err != nil { + return err + } + m.Code, m.Msg, m.Address, m.Signature, m.CommittedSeal = msg.Code, msg.Msg, msg.Address, msg.Signature, msg.CommittedSeal + return nil +} + +// ============================================== +// +// define the functions that needs to be provided for core. + +func (m *message) FromPayload(b []byte, validateFn func([]byte, []byte) (common.Address, error)) error { + // Decode message + err := rlp.DecodeBytes(b, &m) + if err != nil { + return err + } + + // Validate message (on a message without Signature) + if validateFn != nil { + var payload []byte + payload, err = m.PayloadNoSig() + if err != nil { + return err + } + + signerAdd, err := validateFn(payload, m.Signature) + if err != nil { + return err + } + if !bytes.Equal(signerAdd.Bytes(), m.Address.Bytes()) { + return errInvalidSigner + } + } + return nil +} + +func (m *message) Payload() ([]byte, error) { + return rlp.EncodeToBytes(m) +} + +func (m *message) PayloadNoSig() ([]byte, error) { + return rlp.EncodeToBytes(&message{ + Code: m.Code, + Msg: m.Msg, + Address: m.Address, + Signature: []byte{}, + CommittedSeal: m.CommittedSeal, + }) +} + +func (m *message) Decode(val interface{}) error { + return rlp.DecodeBytes(m.Msg, val) +} + +func (m *message) String() string { + return fmt.Sprintf("{Code: %v, Address: %v}", m.Code, m.Address.String()) +} + +// ============================================== +// +// helper functions + +func Encode(val interface{}) ([]byte, error) { + return rlp.EncodeToBytes(val) +} diff --git a/consensus/istanbul/core/types_test.go b/consensus/istanbul/core/types_test.go new file mode 100644 index 0000000000..10dcaa8f1d --- /dev/null +++ b/consensus/istanbul/core/types_test.go @@ -0,0 +1,180 @@ +// Copyright 2017 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 . + +package core + +import ( + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func testPreprepare(t *testing.T) { + pp := &istanbul.Preprepare{ + View: &istanbul.View{ + Round: big.NewInt(1), + Sequence: big.NewInt(2), + }, + Proposal: makeBlock(1), + } + prepreparePayload, _ := Encode(pp) + + m := &message{ + Code: msgPreprepare, + Msg: prepreparePayload, + Address: common.HexToAddress("0x1234567890"), + } + + msgPayload, err := m.Payload() + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + decodedMsg := new(message) + err = decodedMsg.FromPayload(msgPayload, nil) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + var decodedPP *istanbul.Preprepare + err = decodedMsg.Decode(&decodedPP) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + // if block is encoded/decoded by rlp, we cannot to compare interface data type using reflect.DeepEqual. (like istanbul.Proposal) + // so individual comparison here. + if !reflect.DeepEqual(pp.Proposal.Hash(), decodedPP.Proposal.Hash()) { + t.Errorf("proposal hash mismatch: have %v, want %v", decodedPP.Proposal.Hash(), pp.Proposal.Hash()) + } + + if !reflect.DeepEqual(pp.View, decodedPP.View) { + t.Errorf("view mismatch: have %v, want %v", decodedPP.View, pp.View) + } + + if !reflect.DeepEqual(pp.Proposal.Number(), decodedPP.Proposal.Number()) { + t.Errorf("proposal number mismatch: have %v, want %v", decodedPP.Proposal.Number(), pp.Proposal.Number()) + } +} + +func testSubject(t *testing.T) { + s := &istanbul.Subject{ + View: &istanbul.View{ + Round: big.NewInt(1), + Sequence: big.NewInt(2), + }, + Digest: common.StringToHash("1234567890"), + } + + subjectPayload, _ := Encode(s) + + m := &message{ + Code: msgPreprepare, + Msg: subjectPayload, + Address: common.HexToAddress("0x1234567890"), + } + + msgPayload, err := m.Payload() + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + decodedMsg := new(message) + err = decodedMsg.FromPayload(msgPayload, nil) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + var decodedSub *istanbul.Subject + err = decodedMsg.Decode(&decodedSub) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + if !reflect.DeepEqual(s, decodedSub) { + t.Errorf("subject mismatch: have %v, want %v", decodedSub, s) + } +} + +func testSubjectWithSignature(t *testing.T) { + s := &istanbul.Subject{ + View: &istanbul.View{ + Round: big.NewInt(1), + Sequence: big.NewInt(2), + }, + Digest: common.StringToHash("1234567890"), + } + expectedSig := []byte{0x01} + + subjectPayload, _ := Encode(s) + // 1. Encode test + address := common.HexToAddress("0x1234567890") + m := &message{ + Code: msgPreprepare, + Msg: subjectPayload, + Address: address, + Signature: expectedSig, + CommittedSeal: []byte{}, + } + + msgPayload, err := m.Payload() + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + // 2. Decode test + // 2.1 Test normal validate func + decodedMsg := new(message) + err = decodedMsg.FromPayload(msgPayload, func(data []byte, sig []byte) (common.Address, error) { + return address, nil + }) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + + if !reflect.DeepEqual(decodedMsg, m) { + t.Errorf("error mismatch: have %v, want nil", err) + } + + // 2.2 Test nil validate func + decodedMsg = new(message) + err = decodedMsg.FromPayload(msgPayload, nil) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(decodedMsg, m) { + t.Errorf("message mismatch: have %v, want %v", decodedMsg, m) + } + + // 2.3 Test failed validate func + decodedMsg = new(message) + err = decodedMsg.FromPayload(msgPayload, func(data []byte, sig []byte) (common.Address, error) { + return common.Address{}, istanbul.ErrUnauthorizedAddress + }) + if err != istanbul.ErrUnauthorizedAddress { + t.Errorf("error mismatch: have %v, want %v", err, istanbul.ErrUnauthorizedAddress) + } +} + +func TestMessageEncodeDecode(t *testing.T) { + testPreprepare(t) + testSubject(t) + testSubjectWithSignature(t) +} diff --git a/consensus/istanbul/errors.go b/consensus/istanbul/errors.go new file mode 100644 index 0000000000..ed5b62342f --- /dev/null +++ b/consensus/istanbul/errors.go @@ -0,0 +1,29 @@ +// Copyright 2017 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 . + +package istanbul + +import "errors" + +var ( + // ErrUnauthorizedAddress is returned when given address cannot be found in + // current validator set. + ErrUnauthorizedAddress = errors.New("unauthorized address") + // ErrStoppedEngine is returned if the engine is stopped + ErrStoppedEngine = errors.New("stopped engine") + // ErrStartedEngine is returned if the engine is already started + ErrStartedEngine = errors.New("started engine") +) diff --git a/consensus/istanbul/events.go b/consensus/istanbul/events.go new file mode 100644 index 0000000000..fb6e5bd9c2 --- /dev/null +++ b/consensus/istanbul/events.go @@ -0,0 +1,31 @@ +// Copyright 2017 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 . + +package istanbul + +// RequestEvent is posted to propose a proposal +type RequestEvent struct { + Proposal Proposal +} + +// MessageEvent is posted for Istanbul engine communication +type MessageEvent struct { + Payload []byte +} + +// FinalCommittedEvent is posted when a proposal is committed +type FinalCommittedEvent struct { +} diff --git a/consensus/istanbul/types.go b/consensus/istanbul/types.go new file mode 100644 index 0000000000..86b586a2d0 --- /dev/null +++ b/consensus/istanbul/types.go @@ -0,0 +1,147 @@ +// Copyright 2017 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 . + +package istanbul + +import ( + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Proposal supports retrieving height and serialized block to be used during Istanbul consensus. +type Proposal interface { + // Number retrieves the sequence number of this proposal. + Number() *big.Int + + // Hash retrieves the hash of this proposal. + Hash() common.Hash + + EncodeRLP(w io.Writer) error + + DecodeRLP(s *rlp.Stream) error + + String() string +} + +type Request struct { + Proposal Proposal +} + +// View includes a round number and a sequence number. +// Sequence is the block number we'd like to commit. +// Each round has a number and is composed by 3 steps: preprepare, prepare and commit. +// +// If the given block is not accepted by validators, a round change will occur +// and the validators start a new round with round+1. +type View struct { + Round *big.Int + Sequence *big.Int +} + +// EncodeRLP serializes b into the Ethereum RLP format. +func (v *View) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{v.Round, v.Sequence}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (v *View) DecodeRLP(s *rlp.Stream) error { + var view struct { + Round *big.Int + Sequence *big.Int + } + + if err := s.Decode(&view); err != nil { + return err + } + v.Round, v.Sequence = view.Round, view.Sequence + return nil +} + +func (v *View) String() string { + return fmt.Sprintf("{Round: %d, Sequence: %d}", v.Round.Uint64(), v.Sequence.Uint64()) +} + +// Cmp compares v and y and returns: +// -1 if v < y +// 0 if v == y +// +1 if v > y +func (v *View) Cmp(y *View) int { + if v.Sequence.Cmp(y.Sequence) != 0 { + return v.Sequence.Cmp(y.Sequence) + } + if v.Round.Cmp(y.Round) != 0 { + return v.Round.Cmp(y.Round) + } + return 0 +} + +type Preprepare struct { + View *View + Proposal Proposal +} + +// EncodeRLP serializes b into the Ethereum RLP format. +func (b *Preprepare) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{b.View, b.Proposal}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (b *Preprepare) DecodeRLP(s *rlp.Stream) error { + var preprepare struct { + View *View + Proposal *types.Block + } + + if err := s.Decode(&preprepare); err != nil { + return err + } + b.View, b.Proposal = preprepare.View, preprepare.Proposal + + return nil +} + +type Subject struct { + View *View + Digest common.Hash +} + +// EncodeRLP serializes b into the Ethereum RLP format. +func (b *Subject) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{b.View, b.Digest}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (b *Subject) DecodeRLP(s *rlp.Stream) error { + var subject struct { + View *View + Digest common.Hash + } + + if err := s.Decode(&subject); err != nil { + return err + } + b.View, b.Digest = subject.View, subject.Digest + return nil +} + +func (b *Subject) String() string { + return fmt.Sprintf("{View: %v, Digest: %v}", b.View, b.Digest.String()) +} diff --git a/consensus/istanbul/types_test.go b/consensus/istanbul/types_test.go new file mode 100644 index 0000000000..cc23d486f1 --- /dev/null +++ b/consensus/istanbul/types_test.go @@ -0,0 +1,71 @@ +// Copyright 2017 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 . + +package istanbul + +import ( + "math/big" + "testing" +) + +func TestViewCompare(t *testing.T) { + // test equality + srvView := &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(1), + } + tarView := &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(1), + } + if r := srvView.Cmp(tarView); r != 0 { + t.Errorf("source(%v) should be equal to target(%v): have %v, want %v", srvView, tarView, r, 0) + } + + // test larger Sequence + tarView = &View{ + Sequence: big.NewInt(1), + Round: big.NewInt(1), + } + if r := srvView.Cmp(tarView); r != 1 { + t.Errorf("source(%v) should be larger than target(%v): have %v, want %v", srvView, tarView, r, 1) + } + + // test larger Round + tarView = &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(0), + } + if r := srvView.Cmp(tarView); r != 1 { + t.Errorf("source(%v) should be larger than target(%v): have %v, want %v", srvView, tarView, r, 1) + } + + // test smaller Sequence + tarView = &View{ + Sequence: big.NewInt(3), + Round: big.NewInt(1), + } + if r := srvView.Cmp(tarView); r != -1 { + t.Errorf("source(%v) should be smaller than target(%v): have %v, want %v", srvView, tarView, r, -1) + } + tarView = &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(2), + } + if r := srvView.Cmp(tarView); r != -1 { + t.Errorf("source(%v) should be smaller than target(%v): have %v, want %v", srvView, tarView, r, -1) + } +} diff --git a/consensus/istanbul/utils.go b/consensus/istanbul/utils.go new file mode 100644 index 0000000000..1382ad03e6 --- /dev/null +++ b/consensus/istanbul/utils.go @@ -0,0 +1,60 @@ +// Copyright 2017 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 . + +package istanbul + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +func RLPHash(v interface{}) (h common.Hash) { + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, v) + hw.Sum(h[:0]) + return h +} + +// GetSignatureAddress gets the signer address from the signature +func GetSignatureAddress(data []byte, sig []byte) (common.Address, error) { + // 1. Keccak data + hashData := crypto.Keccak256(data) + // 2. Recover public key + pubkey, err := crypto.SigToPub(hashData, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*pubkey), nil +} + +func CheckValidatorSignature(valSet ValidatorSet, data []byte, sig []byte) (common.Address, error) { + // 1. Get signature address + signer, err := GetSignatureAddress(data, sig) + if err != nil { + log.Error("Failed to get signer address", "err", err) + return common.Address{}, err + } + + // 2. Check validator + if _, val := valSet.GetByAddress(signer); val != nil { + return val.Address(), nil + } + + return common.Address{}, ErrUnauthorizedAddress +} diff --git a/consensus/istanbul/validator.go b/consensus/istanbul/validator.go new file mode 100644 index 0000000000..e0d142866e --- /dev/null +++ b/consensus/istanbul/validator.go @@ -0,0 +1,80 @@ +// Copyright 2017 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 . + +package istanbul + +import ( + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +type Validator interface { + // Address returns address + Address() common.Address + + // String representation of Validator + String() string +} + +// ---------------------------------------------------------------------------- + +type Validators []Validator + +func (slice Validators) Len() int { + return len(slice) +} + +func (slice Validators) Less(i, j int) bool { + return strings.Compare(slice[i].String(), slice[j].String()) < 0 +} + +func (slice Validators) Swap(i, j int) { + slice[i], slice[j] = slice[j], slice[i] +} + +// ---------------------------------------------------------------------------- + +type ValidatorSet interface { + // Calculate the proposer + CalcProposer(lastProposer common.Address, round uint64) + // Return the validator size + Size() int + // Return the validator array + List() []Validator + // Get validator by index + GetByIndex(i uint64) Validator + // Get validator by given address + GetByAddress(addr common.Address) (int, Validator) + // Get current proposer + GetProposer() Validator + // Check whether the validator with given address is a proposer + IsProposer(address common.Address) bool + // Add validator + AddValidator(address common.Address) bool + // Remove validator + RemoveValidator(address common.Address) bool + // Copy validator set + Copy() ValidatorSet + // Get the maximum number of faulty nodes + F() int + // Get proposer policy + Policy() ProposerPolicy +} + +// ---------------------------------------------------------------------------- + +type ProposalSelector func(ValidatorSet, common.Address, uint64) Validator diff --git a/consensus/istanbul/validator/default.go b/consensus/istanbul/validator/default.go new file mode 100644 index 0000000000..17edda5521 --- /dev/null +++ b/consensus/istanbul/validator/default.go @@ -0,0 +1,201 @@ +// Copyright 2017 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 . + +package validator + +import ( + "math" + "reflect" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +type defaultValidator struct { + address common.Address +} + +func (val *defaultValidator) Address() common.Address { + return val.address +} + +func (val *defaultValidator) String() string { + return val.Address().String() +} + +// ---------------------------------------------------------------------------- + +type defaultSet struct { + validators istanbul.Validators + policy istanbul.ProposerPolicy + + proposer istanbul.Validator + validatorMu sync.RWMutex + selector istanbul.ProposalSelector +} + +func newDefaultSet(addrs []common.Address, policy istanbul.ProposerPolicy) *defaultSet { + valSet := &defaultSet{} + + valSet.policy = policy + // init validators + valSet.validators = make([]istanbul.Validator, len(addrs)) + for i, addr := range addrs { + valSet.validators[i] = New(addr) + } + // sort validator + sort.Sort(valSet.validators) + // init proposer + if valSet.Size() > 0 { + valSet.proposer = valSet.GetByIndex(0) + } + valSet.selector = roundRobinProposer + if policy == istanbul.Sticky { + valSet.selector = stickyProposer + } + + return valSet +} + +func (valSet *defaultSet) Size() int { + valSet.validatorMu.RLock() + defer valSet.validatorMu.RUnlock() + return len(valSet.validators) +} + +func (valSet *defaultSet) List() []istanbul.Validator { + valSet.validatorMu.RLock() + defer valSet.validatorMu.RUnlock() + return valSet.validators +} + +func (valSet *defaultSet) GetByIndex(i uint64) istanbul.Validator { + valSet.validatorMu.RLock() + defer valSet.validatorMu.RUnlock() + if i < uint64(valSet.Size()) { + return valSet.validators[i] + } + return nil +} + +func (valSet *defaultSet) GetByAddress(addr common.Address) (int, istanbul.Validator) { + for i, val := range valSet.List() { + if addr == val.Address() { + return i, val + } + } + return -1, nil +} + +func (valSet *defaultSet) GetProposer() istanbul.Validator { + return valSet.proposer +} + +func (valSet *defaultSet) IsProposer(address common.Address) bool { + _, val := valSet.GetByAddress(address) + return reflect.DeepEqual(valSet.GetProposer(), val) +} + +func (valSet *defaultSet) CalcProposer(lastProposer common.Address, round uint64) { + valSet.validatorMu.RLock() + defer valSet.validatorMu.RUnlock() + valSet.proposer = valSet.selector(valSet, lastProposer, round) +} + +func calcSeed(valSet istanbul.ValidatorSet, proposer common.Address, round uint64) uint64 { + offset := 0 + if idx, val := valSet.GetByAddress(proposer); val != nil { + offset = idx + } + return uint64(offset) + round +} + +func emptyAddress(addr common.Address) bool { + return addr == common.Address{} +} + +func roundRobinProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64) istanbul.Validator { + if valSet.Size() == 0 { + return nil + } + seed := uint64(0) + if emptyAddress(proposer) { + seed = round + } else { + seed = calcSeed(valSet, proposer, round) + 1 + } + pick := seed % uint64(valSet.Size()) + return valSet.GetByIndex(pick) +} + +func stickyProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64) istanbul.Validator { + if valSet.Size() == 0 { + return nil + } + seed := uint64(0) + if emptyAddress(proposer) { + seed = round + } else { + seed = calcSeed(valSet, proposer, round) + } + pick := seed % uint64(valSet.Size()) + return valSet.GetByIndex(pick) +} + +func (valSet *defaultSet) AddValidator(address common.Address) bool { + valSet.validatorMu.Lock() + defer valSet.validatorMu.Unlock() + for _, v := range valSet.validators { + if v.Address() == address { + return false + } + } + valSet.validators = append(valSet.validators, New(address)) + // TODO: we may not need to re-sort it again + // sort validator + sort.Sort(valSet.validators) + return true +} + +func (valSet *defaultSet) RemoveValidator(address common.Address) bool { + valSet.validatorMu.Lock() + defer valSet.validatorMu.Unlock() + + for i, v := range valSet.validators { + if v.Address() == address { + valSet.validators = append(valSet.validators[:i], valSet.validators[i+1:]...) + return true + } + } + return false +} + +func (valSet *defaultSet) Copy() istanbul.ValidatorSet { + valSet.validatorMu.RLock() + defer valSet.validatorMu.RUnlock() + + addresses := make([]common.Address, 0, len(valSet.validators)) + for _, v := range valSet.validators { + addresses = append(addresses, v.Address()) + } + return NewSet(addresses, valSet.policy) +} + +func (valSet *defaultSet) F() int { return int(math.Ceil(float64(valSet.Size())/3)) - 1 } + +func (valSet *defaultSet) Policy() istanbul.ProposerPolicy { return valSet.policy } diff --git a/consensus/istanbul/validator/default_test.go b/consensus/istanbul/validator/default_test.go new file mode 100644 index 0000000000..486d4861b1 --- /dev/null +++ b/consensus/istanbul/validator/default_test.go @@ -0,0 +1,209 @@ +// Copyright 2017 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 . + +package validator + +import ( + fmt "fmt" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + testAddress = "70524d664ffe731100208a0154e556f9bb679ae6" + testAddress2 = "b37866a925bccd69cfa98d43b510f1d23d78a851" +) + +func TestValidatorSet(t *testing.T) { + testNewValidatorSet(t) + testNormalValSet(t) + testEmptyValSet(t) + testStickyProposer(t) + testAddAndRemoveValidator(t) +} + +func testNewValidatorSet(t *testing.T) { + var validators []istanbul.Validator + const ValCnt = 100 + + // Create 100 validators with random addresses + b := []byte{} + for i := 0; i < ValCnt; i++ { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + val := New(addr) + validators = append(validators, val) + b = append(b, val.Address().Bytes()...) + } + + // Create ValidatorSet + valSet := NewSet(ExtractValidators(b), istanbul.RoundRobin) + if valSet == nil { + t.Errorf("the validator byte array cannot be parsed") + t.FailNow() + } + + // Check validators sorting: should be in ascending order + for i := 0; i < ValCnt-1; i++ { + val := valSet.GetByIndex(uint64(i)) + nextVal := valSet.GetByIndex(uint64(i + 1)) + if strings.Compare(val.String(), nextVal.String()) >= 0 { + t.Errorf("validator set is not sorted in ascending order") + } + } +} + +func testNormalValSet(t *testing.T) { + b1 := common.Hex2Bytes(testAddress) + b2 := common.Hex2Bytes(testAddress2) + addr1 := common.BytesToAddress(b1) + addr2 := common.BytesToAddress(b2) + val1 := New(addr1) + val2 := New(addr2) + + valSet := newDefaultSet([]common.Address{addr1, addr2}, istanbul.RoundRobin) + if valSet == nil { + t.Errorf("the format of validator set is invalid") + t.FailNow() + } + + // check size + if size := valSet.Size(); size != 2 { + t.Errorf("the size of validator set is wrong: have %v, want 2", size) + } + // test get by index + if val := valSet.GetByIndex(uint64(0)); !reflect.DeepEqual(val, val1) { + t.Errorf("validator mismatch: have %v, want %v", val, val1) + } + // test get by invalid index + if val := valSet.GetByIndex(uint64(2)); val != nil { + t.Errorf("validator mismatch: have %v, want nil", val) + } + // test get by address + if _, val := valSet.GetByAddress(addr2); !reflect.DeepEqual(val, val2) { + t.Errorf("validator mismatch: have %v, want %v", val, val2) + } + // test get by invalid address + invalidAddr := common.HexToAddress("0x9535b2e7faaba5288511d89341d94a38063a349b") + if _, val := valSet.GetByAddress(invalidAddr); val != nil { + t.Errorf("validator mismatch: have %v, want nil", val) + } + // test get proposer + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { + t.Errorf("proposer mismatch: have %v, want %v", val, val1) + } + // test calculate proposer + lastProposer := addr1 + valSet.CalcProposer(lastProposer, uint64(0)) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { + t.Errorf("proposer mismatch: have %v, want %v", val, val2) + } + valSet.CalcProposer(lastProposer, uint64(3)) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { + t.Errorf("proposer mismatch: have %v, want %v", val, val1) + } + // test empty last proposer + lastProposer = common.Address{} + valSet.CalcProposer(lastProposer, uint64(3)) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { + t.Errorf("proposer mismatch: have %v, want %v", val, val2) + } +} + +func testEmptyValSet(t *testing.T) { + valSet := NewSet(ExtractValidators([]byte{}), istanbul.RoundRobin) + if valSet == nil { + t.Errorf("validator set should not be nil") + } +} + +func testAddAndRemoveValidator(t *testing.T) { + valSet := NewSet(ExtractValidators([]byte{}), istanbul.RoundRobin) + if !valSet.AddValidator(common.StringToAddress("2")) { + t.Error("the validator should be added") + } + if valSet.AddValidator(common.StringToAddress("2")) { + t.Error("the existing validator should not be added") + } + valSet.AddValidator(common.StringToAddress("1")) + valSet.AddValidator(common.StringToAddress("0")) + if len(valSet.List()) != 3 { + t.Error("the size of validator set should be 3") + } + + for i, v := range valSet.List() { + expected := common.StringToAddress((fmt.Sprint(i))) + if v.Address() != expected { + t.Errorf("the order of validators is wrong: have %v, want %v", v.Address().Hex(), expected.Hex()) + } + } + + if !valSet.RemoveValidator(common.StringToAddress("2")) { + t.Error("the validator should be removed") + } + if valSet.RemoveValidator(common.StringToAddress("2")) { + t.Error("the non-existing validator should not be removed") + } + if len(valSet.List()) != 2 { + t.Error("the size of validator set should be 2") + } + valSet.RemoveValidator(common.StringToAddress("1")) + if len(valSet.List()) != 1 { + t.Error("the size of validator set should be 1") + } + valSet.RemoveValidator(common.StringToAddress("0")) + if len(valSet.List()) != 0 { + t.Error("the size of validator set should be 0") + } +} + +func testStickyProposer(t *testing.T) { + b1 := common.Hex2Bytes(testAddress) + b2 := common.Hex2Bytes(testAddress2) + addr1 := common.BytesToAddress(b1) + addr2 := common.BytesToAddress(b2) + val1 := New(addr1) + val2 := New(addr2) + + valSet := newDefaultSet([]common.Address{addr1, addr2}, istanbul.Sticky) + + // test get proposer + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { + t.Errorf("proposer mismatch: have %v, want %v", val, val1) + } + // test calculate proposer + lastProposer := addr1 + valSet.CalcProposer(lastProposer, uint64(0)) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { + t.Errorf("proposer mismatch: have %v, want %v", val, val1) + } + + valSet.CalcProposer(lastProposer, uint64(1)) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { + t.Errorf("proposer mismatch: have %v, want %v", val, val2) + } + // test empty last proposer + lastProposer = common.Address{} + valSet.CalcProposer(lastProposer, uint64(3)) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { + t.Errorf("proposer mismatch: have %v, want %v", val, val2) + } +} diff --git a/consensus/istanbul/validator/validator.go b/consensus/istanbul/validator/validator.go new file mode 100644 index 0000000000..9a1e15c2d8 --- /dev/null +++ b/consensus/istanbul/validator/validator.go @@ -0,0 +1,47 @@ +// Copyright 2017 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 . + +package validator + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func New(addr common.Address) istanbul.Validator { + return &defaultValidator{ + address: addr, + } +} + +func NewSet(addrs []common.Address, policy istanbul.ProposerPolicy) istanbul.ValidatorSet { + return newDefaultSet(addrs, policy) +} + +func ExtractValidators(extraData []byte) []common.Address { + // get the validator addresses + addrs := make([]common.Address, (len(extraData) / common.AddressLength)) + for i := 0; i < len(addrs); i++ { + copy(addrs[i][:], extraData[i*common.AddressLength:]) + } + + return addrs +} + +// Check whether the extraData is presented in prescribed form +func ValidExtraData(extraData []byte) bool { + return len(extraData)%common.AddressLength == 0 +} diff --git a/consensus/protocol.go b/consensus/protocol.go new file mode 100644 index 0000000000..6f65eebfd5 --- /dev/null +++ b/consensus/protocol.go @@ -0,0 +1,77 @@ +// Quorum +package consensus + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Constants to match up protocol versions and messages +// istanbul/99 was added to accommodate new eth/64 handshake status data with fork id +// this is for backward compatibility which allows a mixed old/new istanbul node network +// istanbul/64 will continue using old status data as eth/63 +const ( + eth63 = 63 + eth64 = 64 + eth65 = 65 + Istanbul64 = 64 + Istanbul99 = 99 + // this istanbul subprotocol will be registered in addition to "eth" + Istanbul100 = 100 +) + +var ( + IstanbulProtocol = Protocol{ + Name: "istanbul", + Versions: []uint{Istanbul100, Istanbul99, Istanbul64}, + // istanbul/100 has to have 18 message to be backwards compatible although at the p2p layer it only has + // 1 message with msg.Code 17 + Lengths: map[uint]uint64{Istanbul100: 18, Istanbul99: 18, Istanbul64: 18}, + } + + CliqueProtocol = Protocol{ + Name: "eth", + Versions: []uint{eth65, eth64, eth63}, + Lengths: map[uint]uint64{eth65: 17, eth64: 17, eth63: 17}, + } + + // Default: Keep up-to-date with eth/protocol.go + EthProtocol = Protocol{ + Name: "eth", + Versions: []uint{eth65, eth64, eth63}, + Lengths: map[uint]uint64{eth65: 17, eth64: 17, eth63: 17}, + } + + NorewardsProtocol = Protocol{ + Name: "Norewards", + Versions: []uint{0}, + Lengths: map[uint]uint64{0: 0}, + } +) + +// Protocol defines the protocol of the consensus +type Protocol struct { + // Official short name of the protocol used during capability negotiation. + Name string + // Supported versions of the eth protocol (first is primary). + Versions []uint + // Number of implemented message corresponding to different protocol versions. + Lengths map[uint]uint64 +} + +// Broadcaster defines the interface to enqueue blocks to fetcher and find peer +type Broadcaster interface { + // Enqueue add a block into fetcher queue + Enqueue(id string, block *types.Block) + // FindPeers retrives peers by addresses + FindPeers(map[common.Address]bool) map[common.Address]Peer +} + +// Peer defines the interface to communicate with peer +type Peer interface { + // Send sends the message to this peer + Send(msgcode uint64, data interface{}) error + + // SendConsensus sends the message to this p2p peer using the consensus specific devp2p subprotocol + SendConsensus(msgcode uint64, data interface{}) error +} diff --git a/console/console.go b/console/console.go index 1dcad3065e..1200ad99eb 100644 --- a/console/console.go +++ b/console/console.go @@ -17,6 +17,7 @@ package console import ( + "context" "fmt" "io" "io/ioutil" @@ -196,6 +197,10 @@ func (c *Console) initExtensions() error { if api == "web3" { continue } + //quorum + // the @ symbol results in errors that prevent the extension from being added to the web3 object + api = strings.Replace(api, "plugin@", "plugin_", 1) + //!quorum aliases[api] = struct{}{} if file, ok := web3ext.Modules[api]; ok { if err = c.jsre.Compile(api+".js", file); err != nil { @@ -301,8 +306,26 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str func (c *Console) Welcome() { message := "Welcome to the Geth JavaScript console!\n\n" - // Print some generic Geth metadata - if res, err := c.jsre.Run(` + // Quorum: Block timestamp for Raft is in nanoseconds, so convert accordingly + consensus := c.getConsensus() + if consensus == "raft" { + // Print some generic Geth metadata + if res, err := c.jsre.Run(` + var message = "instance: " + web3.version.node + "\n"; + try { + message += "coinbase: " + eth.coinbase + "\n"; + } catch (err) {} + message += "at block: " + eth.blockNumber + " (" + new Date(eth.getBlock(eth.blockNumber).timestamp / 1000000) + ")\n"; + try { + message += " datadir: " + admin.datadir + "\n"; + } catch (err) {} + message + `); err == nil { + message += res.String() + } + } else { + // Print some generic Geth metadata + if res, err := c.jsre.Run(` var message = "instance: " + web3.version.node + "\n"; try { message += "coinbase: " + eth.coinbase + "\n"; @@ -313,7 +336,8 @@ func (c *Console) Welcome() { } catch (err) {} message `); err == nil { - message += res.String() + message += res.String() + } } // List all the supported modules for the user to call if apis, err := c.client.SupportedModules(); err == nil { @@ -327,6 +351,30 @@ func (c *Console) Welcome() { fmt.Fprintln(c.printer, message) } +// Get the consensus mechanism that is in use +func (c *Console) getConsensus() string { + + var nodeInfo struct { + Protocols struct { + Eth struct { // only partial of eth/handler.go#NodeInfo + Consensus string + } + Istanbul struct { // a bit different from others + Consensus string + } + } + } + + if err := c.client.CallContext(context.Background(), &nodeInfo, "admin_nodeInfo"); err != nil { + _, _ = fmt.Fprintf(c.printer, "WARNING: call to admin.getNodeInfo() failed, unable to determine consensus mechanism\n") + return "unknown" + } + if nodeInfo.Protocols.Istanbul.Consensus != "" { + return nodeInfo.Protocols.Istanbul.Consensus + } + return nodeInfo.Protocols.Eth.Consensus +} + // Evaluate executes code and pretty prints the result to the specified output // stream. func (c *Console) Evaluate(statement string) { diff --git a/containers/docker/develop-alpine/Dockerfile_BASE_30347 b/containers/docker/develop-alpine/Dockerfile_BASE_30347 new file mode 100644 index 0000000000..f3247d1788 --- /dev/null +++ b/containers/docker/develop-alpine/Dockerfile_BASE_30347 @@ -0,0 +1,14 @@ +FROM alpine:3.4 + +RUN \ + apk add --update go git make gcc musl-dev && \ + git clone --depth 1 --branch develop https://github.com/ethereum/go-ethereum && \ + (cd go-ethereum && make geth) && \ + cp go-ethereum/build/bin/geth /geth && \ + apk del go git make gcc musl-dev && \ + rm -rf /go-ethereum && rm -rf /var/cache/apk/* + +EXPOSE 8545 +EXPOSE 30303 + +ENTRYPOINT ["/geth"] diff --git a/containers/docker/develop-alpine/Dockerfile_LOCAL_30347 b/containers/docker/develop-alpine/Dockerfile_LOCAL_30347 new file mode 100644 index 0000000000..d239129d53 --- /dev/null +++ b/containers/docker/develop-alpine/Dockerfile_LOCAL_30347 @@ -0,0 +1,14 @@ +FROM alpine:3.5 + +RUN \ + apk add --update go git make gcc musl-dev linux-headers ca-certificates && \ + git clone --depth 1 https://github.com/ethereum/go-ethereum && \ + (cd go-ethereum && make geth) && \ + cp go-ethereum/build/bin/geth /geth && \ + apk del go git make gcc musl-dev linux-headers && \ + rm -rf /go-ethereum && rm -rf /var/cache/apk/* + +EXPOSE 8545 +EXPOSE 30303 + +ENTRYPOINT ["/geth"] diff --git a/containers/vagrant/.gitignore b/containers/vagrant/.gitignore new file mode 100644 index 0000000000..8000dd9db4 --- /dev/null +++ b/containers/vagrant/.gitignore @@ -0,0 +1 @@ +.vagrant diff --git a/containers/vagrant/provisioners/shell/centos.sh b/containers/vagrant/provisioners/shell/centos.sh new file mode 100755 index 0000000000..744da4bfd4 --- /dev/null +++ b/containers/vagrant/provisioners/shell/centos.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +sudo yum install -y git wget +sudo yum update -y + +wget --continue https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz +sudo tar -C /usr/local -xzf go1.8.1.linux-amd64.tar.gz + +GETH_PATH="~vagrant/go/src/github.com/ethereum/go-ethereum/build/bin/" + +echo "export PATH=$PATH:/usr/local/go/bin:$GETH_PATH" >> ~vagrant/.bashrc diff --git a/containers/vagrant/provisioners/shell/debian.sh b/containers/vagrant/provisioners/shell/debian.sh new file mode 100755 index 0000000000..1c1793336d --- /dev/null +++ b/containers/vagrant/provisioners/shell/debian.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +sudo apt-get install -y build-essential git-all wget +sudo apt-get update + +wget --continue https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz +sudo tar -C /usr/local -xzf go1.8.1.linux-amd64.tar.gz + +GETH_PATH="~vagrant/go/src/github.com/ethereum/go-ethereum/build/bin/" + +echo "export PATH=$PATH:/usr/local/go/bin:$GETH_PATH" >> ~vagrant/.bashrc diff --git a/containers/vagrant/provisioners/shell/ubuntu.sh b/containers/vagrant/provisioners/shell/ubuntu.sh new file mode 100755 index 0000000000..1c1793336d --- /dev/null +++ b/containers/vagrant/provisioners/shell/ubuntu.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +sudo apt-get install -y build-essential git-all wget +sudo apt-get update + +wget --continue https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz +sudo tar -C /usr/local -xzf go1.8.1.linux-amd64.tar.gz + +GETH_PATH="~vagrant/go/src/github.com/ethereum/go-ethereum/build/bin/" + +echo "export PATH=$PATH:/usr/local/go/bin:$GETH_PATH" >> ~vagrant/.bashrc diff --git a/core/bench_test.go b/core/bench_test.go index 0f4cabd837..e69ad656fc 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -94,6 +94,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { var ( ringKeys = make([]*ecdsa.PrivateKey, 1000) ringAddrs = make([]common.Address, len(ringKeys)) + // bigTxGas = new(big.Int).SetUint64(params.TxGas) ) func init() { diff --git a/core/block_validator.go b/core/block_validator.go index b36ca56d7f..9f2e3155e2 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -77,6 +77,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // transition, such as amount of used gas, the receipt roots and the state root // itself. ValidateState returns a database batch if the validation was a success // otherwise nil and an error is returned. +// +// For quorum it also verifies if the canonical hash in the blocks state points to a valid parent hash. func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error { header := block.Header() if block.GasUsed() != usedGas { @@ -106,10 +108,10 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD // ceil if the blocks are full. If the ceil is exceeded, it will always decrease // the gas allowance. func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 { - // contrib = (parentGasUsed * 3 / 2) / 1024 + // contrib = (parentGasUsed * 3 / 2) / 4096 contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor - // decay = parentGasLimit / 1024 -1 + // decay = parentGasLimit / 4096 -1 decay := parent.GasLimit()/params.GasLimitBoundDivisor - 1 /* diff --git a/core/blockchain.go b/core/blockchain.go index 7742f4ec28..6b86b39694 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -18,6 +18,7 @@ package core import ( + "context" "errors" "fmt" "io" @@ -28,7 +29,10 @@ 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" "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/consensus" @@ -189,9 +193,25 @@ type BlockChain struct { processor Processor // Block transaction processor interface vmConfig vm.Config - badBlocks *lru.Cache // Bad block cache - shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. - terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. + badBlocks *lru.Cache // Bad block cache + shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. + terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. + 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 +func (bc *BlockChain) PopulateSetPrivateState(ps func([]*types.Log, *state.StateDB)) { + bc.setPrivateState = ps +} + +// function to update the private state as a part contract state extension +func (bc *BlockChain) CheckAndSetPrivateState(txLogs []*types.Log, privateState *state.StateDB) { + if bc.setPrivateState != nil { + bc.setPrivateState(txLogs, privateState) + } } // NewBlockChain returns a fully initialised block chain using information @@ -216,22 +236,23 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par badBlocks, _ := lru.New(badBlockLimit) bc := &BlockChain{ - chainConfig: chainConfig, - cacheConfig: cacheConfig, - db: db, - triegc: prque.New(nil), - stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit), - quit: make(chan struct{}), - shouldPreserve: shouldPreserve, - bodyCache: bodyCache, - bodyRLPCache: bodyRLPCache, - receiptsCache: receiptsCache, - blockCache: blockCache, - txLookupCache: txLookupCache, - futureBlocks: futureBlocks, - engine: engine, - vmConfig: vmConfig, - badBlocks: badBlocks, + chainConfig: chainConfig, + cacheConfig: cacheConfig, + db: db, + triegc: prque.New(nil), + stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit), + quit: make(chan struct{}), + shouldPreserve: shouldPreserve, + bodyCache: bodyCache, + bodyRLPCache: bodyRLPCache, + receiptsCache: receiptsCache, + blockCache: blockCache, + txLookupCache: txLookupCache, + futureBlocks: futureBlocks, + engine: engine, + vmConfig: vmConfig, + badBlocks: badBlocks, + privateStateCache: state.NewDatabase(db), } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) @@ -331,6 +352,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par return bc, nil } +// Quorum +func NewMultitenantBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool, txLookupLimit *uint64) (*BlockChain, error) { + bc, err := NewBlockChain(db, cacheConfig, chainConfig, engine, vmConfig, shouldPreserve, txLookupLimit) + if err != nil { + return nil, err + } + bc.isMultitenant = true + return bc, err +} + +// End Quorum + // GetVMConfig returns the block chain VM config. func (bc *BlockChain) GetVMConfig() *vm.Config { return &bc.vmConfig @@ -376,6 +409,14 @@ func (bc *BlockChain) loadLastState() error { } rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash()) } + + // Quorum + if _, err := state.New(rawdb.GetPrivateStateRoot(bc.db, currentBlock.Root()), bc.privateStateCache, nil); err != nil { + log.Warn("Head private state missing, resetting chain", "number", currentBlock.Number(), "hash", currentBlock.Hash()) + return bc.Reset() + } + // /Quorum + // Everything seems to be fine, set as the head block bc.currentBlock.Store(currentBlock) headBlockGauge.Update(int64(currentBlock.NumberU64())) @@ -525,7 +566,14 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { // GasLimit returns the gas limit of the current HEAD block. func (bc *BlockChain) GasLimit() uint64 { - return bc.CurrentBlock().GasLimit() + bc.chainmu.RLock() + defer bc.chainmu.RUnlock() + + if bc.Config().IsQuorum { + return math.MaxBig256.Uint64() // HACK(joel) a very large number + } else { + return bc.CurrentBlock().GasLimit() + } } // CurrentBlock retrieves the current head block of the canonical chain. The @@ -560,18 +608,22 @@ func (bc *BlockChain) Processor() Processor { } // State returns a new mutable state based on the current HEAD block. -func (bc *BlockChain) State() (*state.StateDB, error) { +func (bc *BlockChain) State() (*state.StateDB, *state.StateDB, error) { return bc.StateAt(bc.CurrentBlock().Root()) } // StateAt returns a new mutable state based on a particular point in time. -func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { - return state.New(root, bc.stateCache, bc.snaps) +func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, *state.StateDB, error) { + publicStateDb, privateStateDb, err := state.NewDual(root, bc.stateCache, bc.snaps, bc.db, bc.privateStateCache, nil) + if err != nil { + return nil, nil, err + } + return publicStateDb, privateStateDb, nil } // StateCache returns the caching database underpinning the blockchain instance. -func (bc *BlockChain) StateCache() state.Database { - return bc.stateCache +func (bc *BlockChain) StateCache() (state.Database, state.Database) { + return bc.stateCache, bc.privateStateCache } // Reset purges the entire blockchain, restoring it to its genesis state. @@ -1398,16 +1450,44 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { } // WriteBlockWithState writes the block and all associated state to the database. -func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { +func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state, privateState *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { bc.chainmu.Lock() defer bc.chainmu.Unlock() - return bc.writeBlockWithState(block, receipts, logs, state, emitHeadEvent) + return bc.writeBlockWithState(block, receipts, logs, state, privateState, emitHeadEvent) +} + +// QUORUM +// checks if the consensus engine is Rfat +func (bc *BlockChain) isRaft() bool { + return bc.chainConfig.IsQuorum && bc.chainConfig.Istanbul == nil && bc.chainConfig.Clique == nil +} + +// function specifically added for Raft consensus. This is called from mintNewBlock +// to commit public and private state using bc.chainmu lock +// added to avoid concurrent map errors in high stress conditions +func (bc *BlockChain) CommitBlockWithState(deleteEmptyObjects bool, state, privateState *state.StateDB) error { + // check if consensus is not Raft + if !bc.isRaft() { + return errors.New("error function can be called only for Raft consensus") + } + + bc.chainmu.Lock() + defer bc.chainmu.Unlock() + if _, err := state.Commit(deleteEmptyObjects); err != nil { + return fmt.Errorf("error committing public state: %v", err) + } + if _, err := privateState.Commit(deleteEmptyObjects); err != nil { + return fmt.Errorf("error committing private state: %v", err) + } + return nil } +// END QUORUM + // writeBlockWithState writes the block and all associated state to the database, // but is expects the chain mutex to be held. -func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state, privateState *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { bc.wg.Add(1) defer bc.wg.Done() @@ -1417,6 +1497,24 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. return NonStatTy, consensus.ErrUnknownAncestor } // Make sure no inconsistent state is leaked during insertion + // Quorum + // Write private state changes to database + privateRoot, err := privateState.Commit(bc.chainConfig.IsEIP158(block.Number())) + if err != nil { + return NonStatTy, err + } + if err := rawdb.WritePrivateStateRoot(bc.db, block.Root(), privateRoot); err != nil { + log.Error("Failed writing private state root", "err", err) + return NonStatTy, err + } + // Explicit commit for privateStateTriedb + privateTriedb := bc.privateStateCache.TrieDB() + // TODO ricardolyn: check if nil argument is fine + if err := privateTriedb.Commit(privateRoot, false, nil); err != nil { + return NonStatTy, err + } + // End Quorum + currentBlock := bc.CurrentBlock() localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64()) externTd := new(big.Int).Add(block.Difficulty(), ptd) @@ -1424,7 +1522,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // Irrelevant of the canonical status, write the block itself to the database. // // Note all the components of block(td, hash->number map, header, body, receipts) - // should be written atomically. BlockBatch is used for containing all components. + // should be written aeth/downloader/downloader.gotomically. BlockBatch is used for containing all components. blockBatch := bc.db.NewBatch() rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd) rawdb.WriteBlock(blockBatch, block) @@ -1435,6 +1533,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } // Commit all cached state changes into underlying memory database. root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) + if err != nil { return NonStatTy, err } @@ -1445,6 +1544,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if err := triedb.Commit(root, false, nil); err != nil { return NonStatTy, err } + } else { // Full but not archive node, do proper garbage collection triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive @@ -1600,6 +1700,27 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { return n, err } +// Given a slice of public receipts and an overlapping (smaller) slice of +// private receipts, return a new slice where the default for each location is +// the public receipt but we take the private receipt in each place we have +// one. +func mergeReceipts(pub, priv types.Receipts) types.Receipts { + m := make(map[common.Hash]*types.Receipt) + for _, receipt := range pub { + m[receipt.TxHash] = receipt + } + for _, receipt := range priv { + m[receipt.TxHash] = receipt + } + + ret := make(types.Receipts, 0, len(pub)) + for _, pubReceipt := range pub { + ret = append(ret, m[pubReceipt.TxHash]) + } + + return ret +} + // insertChain is the internal implementation of InsertChain, which assumes that // 1) chains are contiguous, and 2) The chain mutex is held. // @@ -1611,6 +1732,12 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, error) { // If the chain is terminating, don't even bother starting up if atomic.LoadInt32(&bc.procInterrupt) == 1 { + log.Debug("Premature abort during blocks processing") + // QUORUM + if bc.isRaft() { + // Only returns an error for raft mode + return 0, ErrAbortBlocksProcessing + } return 0, nil } // Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) @@ -1716,6 +1843,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // If the chain is terminating, stop processing blocks if bc.insertStopped() { log.Debug("Abort during block processing") + // QUORUM + if bc.isRaft() { + // Only returns an error for raft mode + return it.index, ErrAbortBlocksProcessing + } + // END QUORUM break } // If the header is a banned one, straight out abort @@ -1768,29 +1901,41 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } + // alias state.New because we introduce a variable named state on the next line + stateNew := state.New + statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) if err != nil { return it.index, err } + // Quorum + privateStateRoot := rawdb.GetPrivateStateRoot(bc.db, parent.Root) + privateState, err := stateNew(privateStateRoot, bc.privateStateCache, nil) + if err != nil { + return it.index, err + } + // /Quorum + // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. var followupInterrupt uint32 if !bc.cacheConfig.TrieCleanNoPrefetch { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) - go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) { - bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) + privatest, _ := stateNew(privateStateRoot, bc.privateStateCache, nil) + go func(start time.Time, followup *types.Block, throwaway, privatest *state.StateDB, interrupt *uint32) { + bc.prefetcher.Prefetch(followup, throwaway, privatest, bc.vmConfig, &followupInterrupt) blockPrefetchExecuteTimer.Update(time.Since(start)) if atomic.LoadUint32(interrupt) == 1 { blockPrefetchInterruptMeter.Mark(1) } - }(time.Now(), followup, throwaway, &followupInterrupt) + }(time.Now(), followup, throwaway, privatest, &followupInterrupt) } } // Process block using the parent state as reference point substart := time.Now() - receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + receipts, privateReceipts, logs, usedGas, err := bc.processor.Process(block, statedb, privateState, bc.vmConfig) if err != nil { bc.reportBlock(block, receipts, err) atomic.StoreUint32(&followupInterrupt, 1) @@ -1817,8 +1962,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er atomic.StoreUint32(&followupInterrupt, 1) return it.index, err } - proctime := time.Since(start) + allReceipts := mergeReceipts(receipts, privateReceipts) + proctime := time.Since(start) // Update the metrics touched during block validation accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete, we can mark them storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete, we can mark them @@ -1827,12 +1973,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // Write the block to the chain and get the status. substart = time.Now() - status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false) + status, err := bc.writeBlockWithState(block, allReceipts, logs, statedb, privateState, false) atomic.StoreUint32(&followupInterrupt, 1) if err != nil { return it.index, err } - + if err := rawdb.WritePrivateBlockBloom(bc.db, block.NumberU64(), privateReceipts); err != nil { + return it.index, err + } // Update the metrics touched during block commit accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them @@ -2287,6 +2435,11 @@ func (bc *BlockChain) BadBlocks() []*types.Block { return blocks } +// HasBadBlock returns whether the block with the hash is a bad block. dep: Istanbul +func (bc *BlockChain) HasBadBlock(hash common.Hash) bool { + return bc.badBlocks.Contains(hash) +} + // addBadBlock adds a bad block to the bad-block LRU cache func (bc *BlockChain) addBadBlock(block *types.Block) { bc.badBlocks.Add(block.Hash(), block) @@ -2457,3 +2610,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 +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 0d810699f6..b4fa68fb4e 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -147,7 +147,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err != nil { return err } - receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) + receipts, _, _, usedGas, err := blockchain.processor.Process(block, statedb, statedb, vm.Config{}) if err != nil { blockchain.reportBlock(block, receipts, err) return err @@ -825,7 +825,7 @@ func TestChainTxReorgs(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{ Config: params.TestChainConfig, - GasLimit: 3141592, + GasLimit: 700000000, Alloc: GenesisAlloc{ addr1: {Balance: big.NewInt(1000000)}, addr2: {Balance: big.NewInt(1000000)}, @@ -1258,7 +1258,7 @@ func TestEIP155Transition(t *testing.T) { funds = big.NewInt(1000000000) deleteAddr = common.Address{1} gspec = &Genesis{ - Config: ¶ms.ChainConfig{ChainID: big.NewInt(1), EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}, + Config: ¶ms.ChainConfig{ChainID: big.NewInt(10), EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}, Alloc: GenesisAlloc{address: {Balance: funds}, deleteAddr: {Balance: new(big.Int)}}, } genesis = gspec.MustCommit(db) @@ -1362,7 +1362,7 @@ func TestEIP161AccountRemoval(t *testing.T) { theAddr = common.Address{1} gspec = &Genesis{ Config: ¶ms.ChainConfig{ - ChainID: big.NewInt(1), + ChainID: big.NewInt(10), HomesteadBlock: new(big.Int), EIP155Block: new(big.Int), EIP150Block: new(big.Int), @@ -1398,7 +1398,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { t.Fatal(err) } - if st, _ := blockchain.State(); !st.Exist(theAddr) { + if st, _, _ := blockchain.State(); !st.Exist(theAddr) { t.Error("expected account to exist") } @@ -1406,7 +1406,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(types.Blocks{blocks[1]}); err != nil { t.Fatal(err) } - if st, _ := blockchain.State(); st.Exist(theAddr) { + if st, _, _ := blockchain.State(); st.Exist(theAddr) { t.Error("account should not exist") } @@ -1414,7 +1414,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(types.Blocks{blocks[2]}); err != nil { t.Fatal(err) } - if st, _ := blockchain.State(); st.Exist(theAddr) { + if st, _, _ := blockchain.State(); st.Exist(theAddr) { t.Error("account should not exist") } } @@ -2622,7 +2622,7 @@ func TestDeleteRecreateSlots(t *testing.T) { if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - statedb, _ := chain.State() + statedb, _, _ := chain.State() // If all is correct, then slot 1 and 2 are zero if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { @@ -2702,7 +2702,7 @@ func TestDeleteRecreateAccount(t *testing.T) { if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - statedb, _ := chain.State() + statedb, _, _ := chain.State() // If all is correct, then both slots are zero if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { @@ -2880,7 +2880,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { if n, err := chain.InsertChain([]*types.Block{block}); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - statedb, _ := chain.State() + statedb, _, _ := chain.State() // If all is correct, then slot 1 and 2 are zero if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { t.Errorf("block %d, got %x exp %x", blockNum, got, exp) @@ -3006,7 +3006,7 @@ func TestInitThenFailCreateContract(t *testing.T) { if err != nil { t.Fatalf("failed to create tester chain: %v", err) } - statedb, _ := chain.State() + statedb, _, _ := chain.State() if got, exp := statedb.GetBalance(aa), big.NewInt(100000); got.Cmp(exp) != 0 { t.Fatalf("Genesis err, got %v exp %v", got, exp) } @@ -3016,7 +3016,7 @@ func TestInitThenFailCreateContract(t *testing.T) { if _, err := chain.InsertChain([]*types.Block{blocks[0]}); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) } - statedb, _ = chain.State() + statedb, _, _ = chain.State() if got, exp := statedb.GetBalance(aa), big.NewInt(100000); got.Cmp(exp) != 0 { t.Fatalf("block %d: got %v exp %v", block.NumberU64(), got, exp) } diff --git a/core/call_helper.go b/core/call_helper.go new file mode 100644 index 0000000000..46532b0c10 --- /dev/null +++ b/core/call_helper.go @@ -0,0 +1,100 @@ +package core + +import ( + "crypto/ecdsa" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" +) + +// callHelper makes it easier to do proper calls and use the state transition object. +// It also manages the nonces of the caller and keeps private and public state, which +// can be freely modified outside of the helper. +type callHelper struct { + db ethdb.Database + + nonces map[common.Address]uint64 + header types.Header + gp *GasPool + + PrivateState, PublicState *state.StateDB +} + +// TxNonce returns the pending nonce +func (cg *callHelper) TxNonce(addr common.Address) uint64 { + return cg.nonces[addr] +} + +// MakeCall makes does a call to the recipient using the given input. It can switch between private and public +// by setting the private boolean flag. It returns an error if the call failed. +func (cg *callHelper) MakeCall(private bool, key *ecdsa.PrivateKey, to common.Address, input []byte) error { + var ( + from = crypto.PubkeyToAddress(key.PublicKey) + err error + ) + + // TODO(joel): these are just stubbed to the same values as in dual_state_test.go + cg.header.Number = new(big.Int) + cg.header.Time = uint64(43) + cg.header.Difficulty = new(big.Int).SetUint64(1000488) + cg.header.GasLimit = 4700000 + + signer := types.MakeSigner(params.QuorumTestChainConfig, cg.header.Number) + if private { + signer = types.QuorumPrivateTxSigner{} + } + + tx, err := types.SignTx(types.NewTransaction(cg.TxNonce(from), to, new(big.Int), 1000000, new(big.Int), input), signer, key) + + if err != nil { + return err + } + defer func() { cg.nonces[from]++ }() + msg, err := tx.AsMessage(signer) + if err != nil { + return err + } + + publicState, privateState := cg.PublicState, cg.PrivateState + if !private { + privateState = publicState + } + // TODO(joel): can we just pass nil instead of bc? + bc, _ := NewBlockChain(cg.db, nil, params.QuorumTestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + context := NewEVMContext(msg, &cg.header, bc, &from) + vmenv := vm.NewEVM(context, publicState, privateState, params.QuorumTestChainConfig, vm.Config{}) + sender := vm.AccountRef(msg.From()) + vmenv.Call(sender, to, msg.Data(), 100000000, new(big.Int)) + return err +} + +// MakeCallHelper returns a new callHelper +func MakeCallHelper() *callHelper { + memdb := rawdb.NewMemoryDatabase() + db := state.NewDatabase(memdb) + + publicState, err := state.New(common.Hash{}, db, nil) + if err != nil { + panic(err) + } + privateState, err := state.New(common.Hash{}, db, nil) + if err != nil { + panic(err) + } + cg := &callHelper{ + db: memdb, + nonces: make(map[common.Address]uint64), + gp: new(GasPool).AddGas(5000000), + PublicState: publicState, + PrivateState: privateState, + } + return cg +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 33f253d9e8..c93f1219c0 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -103,7 +103,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { b.SetCoinbase(common.Address{}) } b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) + receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) if err != nil { panic(err) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 85a029f7c7..8ec2091c2f 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -87,7 +87,7 @@ func ExampleGenerateChain() { return } - state, _ := blockchain.State() + state, _, _ := blockchain.State() fmt.Printf("last block: #%d\n", blockchain.CurrentBlock().Number()) fmt.Println("balance of addr1:", state.GetBalance(addr1)) fmt.Println("balance of addr2:", state.GetBalance(addr2)) diff --git a/core/constellation-test-keys/tm1.key b/core/constellation-test-keys/tm1.key new file mode 100644 index 0000000000..2f8c9e6a3c --- /dev/null +++ b/core/constellation-test-keys/tm1.key @@ -0,0 +1 @@ +{"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"} \ No newline at end of file diff --git a/core/constellation-test-keys/tm1.pub b/core/constellation-test-keys/tm1.pub new file mode 100644 index 0000000000..e0f51363c3 --- /dev/null +++ b/core/constellation-test-keys/tm1.pub @@ -0,0 +1 @@ +BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= \ No newline at end of file diff --git a/core/constellation-test-keys/tm1a.key b/core/constellation-test-keys/tm1a.key new file mode 100644 index 0000000000..59e017cbeb --- /dev/null +++ b/core/constellation-test-keys/tm1a.key @@ -0,0 +1 @@ +{"data":{"bytes":"wGEar7J9G0JAgdisp61ZChyrJWeW2QPyKvecjjeVHOY="},"type":"unlocked"} \ No newline at end of file diff --git a/core/constellation-test-keys/tm1a.pub b/core/constellation-test-keys/tm1a.pub new file mode 100644 index 0000000000..eae34e2e93 --- /dev/null +++ b/core/constellation-test-keys/tm1a.pub @@ -0,0 +1 @@ +8SjRHlUBe4hAmTk3KDeJ96RhN+s10xRrHDrxEi1O5W0= \ No newline at end of file diff --git a/core/dual_state_test.go b/core/dual_state_test.go new file mode 100644 index 0000000000..f5f158b04f --- /dev/null +++ b/core/dual_state_test.go @@ -0,0 +1,213 @@ +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +var dualStateTestHeader = types.Header{ + Number: new(big.Int), + Time: 43, + Difficulty: new(big.Int).SetUint64(1000488), + GasLimit: 4700000, +} + +//[1] PUSH1 0x01 (out size) +//[3] PUSH1 0x00 (out offset) +//[5] PUSH1 0x00 (in size) +//[7] PUSH1 0x00 (in offset) +//[9] PUSH1 0x00 (value) +//[30] PUSH20 0x0200000000000000000000000000000000000000 (to) +//[34] PUSH3 0x0186a0 (gas) +//[35] CALL +//[37] PUSH1 0x00 +//[38] MLOAD +//[40] PUSH1 0x00 +//[41] SSTORE +//[42] STOP + +func TestDualStatePrivateToPublicCall(t *testing.T) { + callAddr := common.Address{1} + + db := rawdb.NewMemoryDatabase() + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(common.Address{2}, common.Hex2Bytes("600a6000526001601ff300")) + + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + privateState.SetCode(callAddr, common.Hex2Bytes("60016000600060006000730200000000000000000000000000000000000000620186a0f160005160005500")) + + author := common.Address{} + msg := callmsg{ + addr: author, + to: &callAddr, + value: big.NewInt(1), + gas: 1000000, + gasPrice: new(big.Int), + data: nil, + } + + ctx := NewEVMContext(msg, &dualStateTestHeader, nil, &author) + env := vm.NewEVM(ctx, publicState, privateState, ¶ms.ChainConfig{}, vm.Config{}) + env.Call(vm.AccountRef(author), callAddr, msg.data, msg.gas, new(big.Int)) + + if value := privateState.GetState(callAddr, common.Hash{}); value != (common.Hash{10}) { + t.Errorf("expected 10 got %x", value) + } +} + +func TestDualStatePublicToPrivateCall(t *testing.T) { + callAddr := common.Address{1} + + db := rawdb.NewMemoryDatabase() + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + privateState.SetCode(common.Address{2}, common.Hex2Bytes("600a6000526001601ff300")) + + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(callAddr, common.Hex2Bytes("60016000600060006000730200000000000000000000000000000000000000620186a0f160005160005500")) + + author := common.Address{} + msg := callmsg{ + addr: author, + to: &callAddr, + value: big.NewInt(1), + gas: 1000000, + gasPrice: new(big.Int), + data: nil, + } + + ctx := NewEVMContext(msg, &dualStateTestHeader, nil, &author) + env := vm.NewEVM(ctx, publicState, publicState, ¶ms.ChainConfig{}, vm.Config{}) + env.Call(vm.AccountRef(author), callAddr, msg.data, msg.gas, new(big.Int)) + + if value := publicState.GetState(callAddr, common.Hash{}); value != (common.Hash{}) { + t.Errorf("expected 0 got %x", value) + } +} + +func TestDualStateReadOnly(t *testing.T) { + callAddr := common.Address{1} + + db := rawdb.NewMemoryDatabase() + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(common.Address{2}, common.Hex2Bytes("600a60005500")) + + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + privateState.SetCode(callAddr, common.Hex2Bytes("60016000600060006000730200000000000000000000000000000000000000620186a0f160005160005500")) + + author := common.Address{} + msg := callmsg{ + addr: author, + to: &callAddr, + value: big.NewInt(1), + gas: 1000000, + gasPrice: new(big.Int), + data: nil, + } + + ctx := NewEVMContext(msg, &dualStateTestHeader, nil, &author) + env := vm.NewEVM(ctx, publicState, privateState, ¶ms.ChainConfig{}, vm.Config{}) + env.Call(vm.AccountRef(author), callAddr, msg.data, msg.gas, new(big.Int)) + + if value := publicState.GetState(common.Address{2}, common.Hash{}); value != (common.Hash{0}) { + t.Errorf("expected 0 got %x", value) + } +} + +var ( + calleeAddress = common.Address{2} + calleeContractCode = "600a6000526001601ff300" // a function that returns 10 + callerAddress = common.Address{1} + // a functionn that calls the callee's function at its address and return the same value + //000000: PUSH1 0x01 + //000002: PUSH1 0x00 + //000004: PUSH1 0x00 + //000006: PUSH1 0x00 + //000008: PUSH20 0x0200000000000000000000000000000000000000 + //000029: PUSH3 0x0186a0 + //000033: STATICCALL + //000034: PUSH1 0x01 + //000036: PUSH1 0x00 + //000038: RETURN + //000039: STOP + callerContractCode = "6001600060006000730200000000000000000000000000000000000000620186a0fa60016000f300" +) + +func verifyStaticCall(t *testing.T, privateState *state.StateDB, publicState *state.StateDB, expectedHash common.Hash) { + author := common.Address{} + msg := callmsg{ + addr: author, + to: &callerAddress, + value: big.NewInt(1), + gas: 1000000, + gasPrice: new(big.Int), + data: nil, + } + + ctx := NewEVMContext(msg, &dualStateTestHeader, nil, &author) + env := vm.NewEVM(ctx, publicState, privateState, ¶ms.ChainConfig{ + ByzantiumBlock: new(big.Int), + }, vm.Config{}) + + ret, _, err := env.Call(vm.AccountRef(author), callerAddress, msg.data, msg.gas, new(big.Int)) + + if err != nil { + t.Fatalf("Call error: %s", err) + } + value := common.Hash{ret[0]} + if value != expectedHash { + t.Errorf("expected %x got %x", expectedHash, value) + } +} + +func TestStaticCall_whenPublicToPublic(t *testing.T) { + db := rawdb.NewMemoryDatabase() + + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(callerAddress, common.Hex2Bytes(callerContractCode)) + publicState.SetCode(calleeAddress, common.Hex2Bytes(calleeContractCode)) + + verifyStaticCall(t, publicState, publicState, common.Hash{10}) +} + +func TestStaticCall_whenPublicToPrivateInTheParty(t *testing.T) { + db := rawdb.NewMemoryDatabase() + + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + privateState.SetCode(calleeAddress, common.Hex2Bytes(calleeContractCode)) + + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(callerAddress, common.Hex2Bytes(callerContractCode)) + + verifyStaticCall(t, privateState, publicState, common.Hash{10}) +} + +func TestStaticCall_whenPublicToPrivateNotInTheParty(t *testing.T) { + + db := rawdb.NewMemoryDatabase() + + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(callerAddress, common.Hex2Bytes(callerContractCode)) + + verifyStaticCall(t, privateState, publicState, common.Hash{0}) +} + +func TestStaticCall_whenPrivateToPublic(t *testing.T) { + db := rawdb.NewMemoryDatabase() + + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + privateState.SetCode(callerAddress, common.Hex2Bytes(callerContractCode)) + + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState.SetCode(calleeAddress, common.Hex2Bytes(calleeContractCode)) + + verifyStaticCall(t, privateState, publicState, common.Hash{10}) +} diff --git a/core/error.go b/core/error.go index 5a28be7e1c..d3f55ac4d5 100644 --- a/core/error.go +++ b/core/error.go @@ -63,4 +63,21 @@ var ( // ErrIntrinsicGas is returned if the transaction is specified to use less gas // than required to start the invocation. ErrIntrinsicGas = errors.New("intrinsic gas too low") + + // Quorum + // ErrAbortBlocksProcessing is returned if bc.insertChain is interrupted under raft mode + ErrAbortBlocksProcessing = errors.New("abort during blocks processing") + + // ErrContractManagedPartiesCheckFailed is returned if managed parties check has failed for contract + ErrContractManagedPartiesCheckFailed = errors.New("managed parties check has failed for contract") + + // ErrPrivacyMetadataInvalidMerkleRoot is returned if there is an empty MR during the pmh.prepare(...) + ErrPrivacyMetadataInvalidMerkleRoot = errors.New("privacy metadata has empty MR for stateValidation flag") + + // ErrPrivacyEnhancedReceivedWhenDisabled is returned if privacy enhanced transaction received while privacy enhancements are disabled + ErrPrivacyEnhancedReceivedWhenDisabled = errors.New("privacy metadata has empty MR for stateValidation flag") + + // ErrPrivateContractInteractionVerificationFailed is returned if the verification of contract interaction differs from the one returned by Tessera (check pmh.verify(...)) + ErrPrivateContractInteractionVerificationFailed = errors.New("verification of contract interaction differs from the one returned by Tessera") + // End Quorum ) diff --git a/core/events.go b/core/events.go index ac935a137f..8d200f2a29 100644 --- a/core/events.go +++ b/core/events.go @@ -24,6 +24,14 @@ import ( // NewTxsEvent is posted when a batch of transactions enter the transaction pool. type NewTxsEvent struct{ Txs []*types.Transaction } +// PendingLogsEvent is posted pre mining and notifies of pending logs. +type PendingLogsEvent struct { + Logs []*types.Log +} + +// PendingStateEvent is posted pre mining and notifies of pending state changes. +type PendingStateEvent struct{} + // NewMinedBlockEvent is posted when a block has been imported. type NewMinedBlockEvent struct{ Block *types.Block } diff --git a/core/evm.go b/core/evm.go index 8abe5a0477..5ea6ed3e46 100644 --- a/core/evm.go +++ b/core/evm.go @@ -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 @@ -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, @@ -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 diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index b8d670f399..0247a80176 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -229,6 +229,11 @@ func gatherForks(config *params.ChainConfig) []uint64 { if field.Type != reflect.TypeOf(new(big.Int)) { continue } + + // ignoring QIP714Block from fork check + if field.Name == "QIP714Block" { + continue + } // Extract the fork rule block number and aggregate it rule := conf.Field(i).Interface().(*big.Int) if rule != nil { diff --git a/core/genesis.go b/core/genesis.go index d2a8a4798b..5c6ba1b845 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -164,10 +164,25 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } else { log.Info("Writing custom genesis block") } + + // Quorum: Set default transaction size limit if not set in genesis + if genesis.Config.TransactionSizeLimit == 0 { + genesis.Config.TransactionSizeLimit = DefaultTxPoolConfig.TransactionSizeLimit + } + + // Check transaction size limit and max contract code size + err := genesis.Config.IsValid() + if err != nil { + return genesis.Config, common.Hash{}, err + } + + // /Quorum + block, err := genesis.Commit(db) if err != nil { return genesis.Config, common.Hash{}, err } + checkAndPrintPrivacyEnhancementsWarning(genesis.Config) return genesis.Config, block.Hash(), nil } @@ -207,6 +222,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig if storedcfg == nil { log.Warn("Found genesis block without chain config") rawdb.WriteChainConfig(db, stored, newcfg) + checkAndPrintPrivacyEnhancementsWarning(newcfg) return newcfg, stored, nil } // Special case: don't change the existing config of a non-mainnet chain if no new @@ -222,7 +238,14 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig if height == nil { return newcfg, stored, fmt.Errorf("missing block number for head header hash") } - compatErr := storedcfg.CheckCompatible(newcfg, *height) + + // Quorum + if storedcfg.PrivacyEnhancementsBlock == nil { + checkAndPrintPrivacyEnhancementsWarning(newcfg) + } + // End Quorum + + compatErr := storedcfg.CheckCompatible(newcfg, *height, rawdb.GetIsQuorumEIP155Activated(db)) if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 { return newcfg, stored, compatErr } @@ -230,6 +253,14 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig return newcfg, stored, nil } +func checkAndPrintPrivacyEnhancementsWarning(config *params.ChainConfig) { + if config.PrivacyEnhancementsBlock != nil { + log.Warn("Privacy enhancements have been enabled from block height " + config.PrivacyEnhancementsBlock.String() + + ". Please ensure your privacy manager is upgraded and supports privacy enhancements (tessera version 1.*) " + + "otherwise your quorum node will fail to start.") + } +} + func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { switch { case g != nil: diff --git a/core/genesis_test.go b/core/genesis_test.go index 3470d0aa01..21a029fce7 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -17,15 +17,17 @@ package core import ( + "errors" "math/big" "reflect" "testing" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" + + //"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/vm" + //"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" ) @@ -42,10 +44,10 @@ func TestDefaultGenesisBlock(t *testing.T) { } func TestSetupGenesis(t *testing.T) { + // Quorum: customized test cases for quorum var ( - customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50") - customg = Genesis{ - Config: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3)}, + customg = Genesis{ + Config: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3), IsQuorum: true}, Alloc: GenesisAlloc{ {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, }, @@ -86,57 +88,24 @@ func TestSetupGenesis(t *testing.T) { wantConfig: params.MainnetChainConfig, }, { - name: "custom block in DB, genesis == nil", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - customg.MustCommit(db) - return SetupGenesisBlock(db, nil) - }, - wantHash: customghash, - wantConfig: customg.Config, - }, - { - name: "custom block in DB, genesis == ropsten", + name: "genesis with incorrect SizeLimit", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - customg.MustCommit(db) - return SetupGenesisBlock(db, DefaultRopstenGenesisBlock()) - }, - wantErr: &GenesisMismatchError{Stored: customghash, New: params.RopstenGenesisHash}, - wantHash: params.RopstenGenesisHash, - wantConfig: params.RopstenChainConfig, - }, - { - name: "compatible config in DB", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - oldcustomg.MustCommit(db) + customg.Config.TransactionSizeLimit = 100000 + customg.Config.MaxCodeSize = 32 return SetupGenesisBlock(db, &customg) }, - wantHash: customghash, + wantErr: errors.New("Genesis transaction size limit must be between 32 and 128"), wantConfig: customg.Config, }, { - name: "incompatible config in DB", + name: "genesis with incorrect max code size ", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - // Commit the 'old' genesis block with Homestead transition at #2. - // Advance to block #4, past the homestead transition block of customg. - genesis := oldcustomg.MustCommit(db) - - bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}, nil, nil) - defer bc.Stop() - - blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil) - bc.InsertChain(blocks) - bc.CurrentBlock() - // This should return a compatibility error. + customg.Config.TransactionSizeLimit = 64 + customg.Config.MaxCodeSize = 100000 return SetupGenesisBlock(db, &customg) }, - wantHash: customghash, + wantErr: errors.New("Genesis max code size must be between 24 and 128"), wantConfig: customg.Config, - wantErr: ¶ms.ConfigCompatError{ - What: "Homestead fork block", - StoredConfig: big.NewInt(2), - NewConfig: big.NewInt(3), - RewindTo: 1, - }, }, } diff --git a/core/private_state_test.go b/core/private_state_test.go new file mode 100644 index 0000000000..f766c366da --- /dev/null +++ b/core/private_state_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// callmsg is the message type used for call transactions in the private state test +type callmsg struct { + addr common.Address + to *common.Address + gas uint64 + gasPrice *big.Int + value *big.Int + data []byte +} + +// accessor boilerplate to implement core.Message +func (m callmsg) From() common.Address { return m.addr } +func (m callmsg) FromFrontier() common.Address { return m.addr } +func (m callmsg) Nonce() uint64 { return 0 } +func (m callmsg) To() *common.Address { return m.to } +func (m callmsg) GasPrice() *big.Int { return m.gasPrice } +func (m callmsg) Gas() uint64 { return m.gas } +func (m callmsg) Value() *big.Int { return m.value } +func (m callmsg) Data() []byte { return m.data } +func (m callmsg) CheckNonce() bool { return true } + +func ExampleMakeCallHelper() { + var ( + // setup new pair of keys for the calls + key, _ = crypto.GenerateKey() + // create a new helper + helper = MakeCallHelper() + ) + // Private contract address + prvContractAddr := common.Address{1} + // Initialise custom code for private contract + helper.PrivateState.SetCode(prvContractAddr, common.Hex2Bytes("600a60005500")) + // Public contract address + pubContractAddr := common.Address{2} + // Initialise custom code for public contract + helper.PublicState.SetCode(pubContractAddr, common.Hex2Bytes("601460005500")) + + // Make a call to the private contract + err := helper.MakeCall(true, key, prvContractAddr, nil) + if err != nil { + fmt.Println(err) + } + // Make a call to the public contract + err = helper.MakeCall(false, key, pubContractAddr, nil) + if err != nil { + fmt.Println(err) + } + + // Output: + // Private: 10 + // Public: 20 + fmt.Println("Private:", helper.PrivateState.GetState(prvContractAddr, common.Hash{}).Big()) + fmt.Println("Public:", helper.PublicState.GetState(pubContractAddr, common.Hash{}).Big()) +} + +// 600a600055600060006001a1 +// 60 0a, 60 00, 55, 60 00, 60 00, 60 01, a1 +// [1] (0x60) PUSH1 0x0a (store value) +// [3] (0x60) PUSH1 0x00 (store addr) +// [4] (0x55) SSTORE (Store (k-00,v-a)) + +// [6] (0x60) PUSH1 0x00 +// [8] (0x60) PUSH1 0x00 +// [10](0x60) PUSH1 0x01 +// [11](0xa1) LOG1 offset(0x01), len(0x00), topic(0x00) +// +// Store then log +func TestPrivateTransaction(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + helper = MakeCallHelper() + privateState = helper.PrivateState + publicState = helper.PublicState + ) + + prvContractAddr := common.Address{1} + pubContractAddr := common.Address{2} + // SSTORE (K,V) SSTORE(0, 10): 600a600055 + // + + // LOG1 OFFSET LEN TOPIC, LOG1 (a1) 01, 00, 00: 600060006001a1 + privateState.SetCode(prvContractAddr, common.Hex2Bytes("600a600055600060006001a1")) + // SSTORE (K,V) SSTORE(0, 14): 6014600055 + publicState.SetCode(pubContractAddr, common.Hex2Bytes("6014600055")) + + if publicState.Exist(prvContractAddr) { + t.Error("didn't expect private contract address to exist on public state") + } + + // Private transaction 1 + err := helper.MakeCall(true, key, prvContractAddr, nil) + + if err != nil { + t.Fatal(err) + } + stateEntry := privateState.GetState(prvContractAddr, common.Hash{}).Big() + if stateEntry.Cmp(big.NewInt(10)) != 0 { + t.Error("expected state to have 10, got", stateEntry) + } + if len(privateState.Logs()) != 1 { + t.Error("expected private state to have 1 log, got", len(privateState.Logs())) + } + if len(publicState.Logs()) != 0 { + t.Error("expected public state to have 0 logs, got", len(publicState.Logs())) + } + if publicState.Exist(prvContractAddr) { + t.Error("didn't expect private contract address to exist on public state") + } + if !privateState.Exist(prvContractAddr) { + t.Error("expected private contract address to exist on private state") + } + + // Public transaction 1 + err = helper.MakeCall(false, key, pubContractAddr, nil) + if err != nil { + t.Fatal(err) + } + stateEntry = publicState.GetState(pubContractAddr, common.Hash{}).Big() + if stateEntry.Cmp(big.NewInt(20)) != 0 { + t.Error("expected state to have 20, got", stateEntry) + } + + // Private transaction 2 + helper.MakeCall(true, key, prvContractAddr, nil) + stateEntry = privateState.GetState(prvContractAddr, common.Hash{}).Big() + if stateEntry.Cmp(big.NewInt(10)) != 0 { + t.Error("expected state to have 10, got", stateEntry) + } + + if publicState.Exist(prvContractAddr) { + t.Error("didn't expect private contract address to exist on public state") + } + if privateState.Exist(pubContractAddr) { + t.Error("didn't expect public contract address to exist on private state") + } +} diff --git a/core/rawdb/database_quorum.go b/core/rawdb/database_quorum.go new file mode 100644 index 0000000000..4d5f169839 --- /dev/null +++ b/core/rawdb/database_quorum.go @@ -0,0 +1,124 @@ +/* + + +// Copyright 2015 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 . +*/ +package rawdb + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" +) + +var ( + privateRootPrefix = []byte("P") + privateBloomPrefix = []byte("Pb") + quorumEIP155ActivatedPrefix = []byte("quorum155active") + // Quorum + // we introduce a generic approach to store extra data for an account. PrivacyMetadata is wrapped. + // However, this value is kept as-is to support backward compatibility + stateRootToExtraDataRootPrefix = []byte("PSR2PMDR") + // emptyRoot is the known root hash of an empty trie. Duplicate from `trie/trie.go#emptyRoot` + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") +) + +//returns whether we have a chain configuration that can't be updated +//after the EIP155 HF has happened +func GetIsQuorumEIP155Activated(db ethdb.KeyValueReader) bool { + data, _ := db.Get(quorumEIP155ActivatedPrefix) + return len(data) == 1 +} + +// WriteQuorumEIP155Activation writes a flag to the database saying EIP155 HF is enforced +func WriteQuorumEIP155Activation(db ethdb.KeyValueWriter) error { + return db.Put(quorumEIP155ActivatedPrefix, []byte{1}) +} + +func GetPrivateStateRoot(db ethdb.Database, blockRoot common.Hash) common.Hash { + root, _ := db.Get(append(privateRootPrefix, blockRoot[:]...)) + return common.BytesToHash(root) +} + +func GetAccountExtraDataRoot(db ethdb.KeyValueReader, stateRoot common.Hash) common.Hash { + root, _ := db.Get(append(stateRootToExtraDataRootPrefix, stateRoot[:]...)) + return common.BytesToHash(root) +} + +func WritePrivateStateRoot(db ethdb.Database, blockRoot, root common.Hash) error { + return db.Put(append(privateRootPrefix, blockRoot[:]...), root[:]) +} + +// WriteRootHashMapping stores the mapping between root hash of state trie and +// root hash of state.AccountExtraData trie to persistent storage +func WriteRootHashMapping(db ethdb.KeyValueWriter, stateRoot, extraDataRoot common.Hash) error { + return db.Put(append(stateRootToExtraDataRootPrefix, stateRoot[:]...), extraDataRoot[:]) +} + +// WritePrivateBlockBloom creates a bloom filter for the given receipts and saves it to the database +// with the number given as identifier (i.e. block number). +func WritePrivateBlockBloom(db ethdb.Database, number uint64, receipts types.Receipts) error { + rbloom := types.CreateBloom(receipts) + return db.Put(append(privateBloomPrefix, encodeBlockNumber(number)...), rbloom[:]) +} + +// GetPrivateBlockBloom retrieves the private bloom associated with the given number. +func GetPrivateBlockBloom(db ethdb.Database, number uint64) (bloom types.Bloom) { + data, _ := db.Get(append(privateBloomPrefix, encodeBlockNumber(number)...)) + if len(data) > 0 { + bloom = types.BytesToBloom(data) + } + return bloom +} + +// AccountExtraDataLinker maintains mapping between root hash of the state trie +// and root hash of state.AccountExtraData trie +type AccountExtraDataLinker interface { + // GetAccountExtraDataRoot returns the root hash of the state.AccountExtraData trie from + // the given root hash of the state trie. + // + // It returns an empty hash if not found. + GetAccountExtraDataRoot(stateRoot common.Hash) common.Hash + // Link saves the mapping between root hash of the state trie and + // root hash of state.AccountExtraData trie to the persistent storage. + // Don't write the mapping if extraDataRoot is an emptyRoot + Link(stateRoot, extraDataRoot common.Hash) error +} + +// ethdbAccountExtraDataLinker implements AccountExtraDataLinker using ethdb.Database +// as the persistence storage +type ethdbAccountExtraDataLinker struct { + db ethdb.Database +} + +func NewAccountExtraDataLinker(db ethdb.Database) AccountExtraDataLinker { + return ðdbAccountExtraDataLinker{ + db: db, + } +} + +func (pml *ethdbAccountExtraDataLinker) GetAccountExtraDataRoot(stateRoot common.Hash) common.Hash { + return GetAccountExtraDataRoot(pml.db, stateRoot) +} + +func (pml *ethdbAccountExtraDataLinker) Link(stateRoot, extraDataRoot common.Hash) error { + if extraDataRoot != emptyRoot { + return WriteRootHashMapping(pml.db, stateRoot, extraDataRoot) + } + return nil +} diff --git a/core/rawdb/database_quorum_test.go b/core/rawdb/database_quorum_test.go new file mode 100644 index 0000000000..b44c7f56f8 --- /dev/null +++ b/core/rawdb/database_quorum_test.go @@ -0,0 +1,153 @@ +// Copyright 2015 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 . + +package rawdb + +import ( + "errors" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +// Tests that setting the flag for Quorum EIP155 activation read values correctly +func TestIsQuorumEIP155Active(t *testing.T) { + db := NewMemoryDatabase() + + isQuorumEIP155Active := GetIsQuorumEIP155Activated(db) + if isQuorumEIP155Active { + t.Fatal("Quorum EIP155 active read to be set, but wasn't set beforehand") + } + + dbSet := NewMemoryDatabase() + err := WriteQuorumEIP155Activation(dbSet) + + if err != nil { + t.Fatal("unable to write quorum EIP155 activation") + } + + isQuorumEIP155ActiveAfterSetting := GetIsQuorumEIP155Activated(dbSet) + if !isQuorumEIP155ActiveAfterSetting { + t.Fatal("Quorum EIP155 active read to be unset, but was set beforehand") + } +} + +func TestAccountExtraDataLinker_whenLinkingEmptyRoot(t *testing.T) { + db := NewMemoryDatabase() + psr := common.Hash{1} + + linker := NewAccountExtraDataLinker(db) + + err := linker.Link(psr, emptyRoot) + + if err != nil { + t.Fatal("unable to store the link") + } + + value, _ := db.Get(append(stateRootToExtraDataRootPrefix, psr[:]...)) + + if value != nil { + t.Fatal("the mapping should not have been stored") + } +} + +func TestAccountExtraDataLinker_whenLinkingRoots(t *testing.T) { + db := NewMemoryDatabase() + psr := common.Hash{1} + pmr := common.Hash{2} + + linker := NewAccountExtraDataLinker(db) + + err := linker.Link(psr, pmr) + + if err != nil { + t.Fatal("unable to store the link") + } + + value, _ := db.Get(append(stateRootToExtraDataRootPrefix, psr[:]...)) + + if value == nil { + t.Fatal("the mapping should have been stored") + } + + valueHash := common.BytesToHash(value) + + if pmr != valueHash { + t.Fatal("the privacy metadata root does not have the expected value") + } +} + +var errReadOnly = errors.New("unable to write") + +type ReadOnlyDB struct { + memorydb.Database +} + +func (t *ReadOnlyDB) Put(key []byte, value []byte) error { + return errReadOnly +} + +func TestAccountExtraDataLinker_whenError(t *testing.T) { + db := NewDatabase(&ReadOnlyDB{}) + psr := common.Hash{1} + pmr := common.Hash{2} + + linker := NewAccountExtraDataLinker(db) + + err := linker.Link(psr, pmr) + + if err == nil { + t.Fatal("expecting a read only error to be returned") + } + + if err != errReadOnly { + t.Fatal("expecting the read only error to be returned") + } +} + +func TestAccountExtraDataLinker_whenFinding(t *testing.T) { + db := NewMemoryDatabase() + psr := common.Hash{1} + pmr := common.Hash{2} + + err := db.Put(append(stateRootToExtraDataRootPrefix, psr[:]...), pmr[:]) + + if err != nil { + t.Fatal("unable to write to db") + } + + pml := NewAccountExtraDataLinker(db) + + pmrRetrieved := pml.GetAccountExtraDataRoot(psr) + + if pmrRetrieved != pmr { + t.Fatal("the mapping should have been retrieved") + } +} + +func TestAccountExtraDataLinker_whenNotFound(t *testing.T) { + db := NewMemoryDatabase() + psr := common.Hash{1} + + pml := NewAccountExtraDataLinker(db) + + pmrRetrieved := pml.GetAccountExtraDataRoot(psr) + + if !common.EmptyHash(pmrRetrieved) { + t.Fatal("the retrieved privacy metadata root should be the empty hash") + } +} diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 9a40b2cf43..c5c3c46e2d 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -128,8 +128,17 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { // Close terminates the chain freezer, unmapping all the data files. func (f *freezer) Close() error { - f.quit <- struct{}{} var errs []error + + // Quorum + // Check if 'f.quit' has subscribers, as freezer.Close() might be called again by Raft when stopping raft service + select { + case f.quit <- struct{}{}: + default: + errs = append(errs, errors.New("freezer DB process already stopped")) + } + // End Quorum + for _, table := range f.tables { if err := table.Close(); err != nil { errs = append(errs, err) @@ -287,12 +296,12 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { backoff = true continue - case *number < params.FullImmutabilityThreshold: - log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.FullImmutabilityThreshold) + case *number < uint64(params.GetImmutabilityThreshold()): + log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.GetImmutabilityThreshold()) backoff = true continue - case *number-params.FullImmutabilityThreshold <= f.frozen: + case *number-uint64(params.GetImmutabilityThreshold()) <= f.frozen: log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) backoff = true continue @@ -304,7 +313,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { continue } // Seems we have data ready to be frozen, process in usable batches - limit := *number - params.FullImmutabilityThreshold + limit := *number - uint64(params.GetImmutabilityThreshold()) if limit-f.frozen > freezerBatchLimit { limit = f.frozen + freezerBatchLimit } diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go new file mode 100644 index 0000000000..00e9a97866 --- /dev/null +++ b/core/rawdb/freezer_test.go @@ -0,0 +1,75 @@ +package rawdb + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// Quorum + +func Test_freezer_Close(t *testing.T) { + // Close first time + mockFreezer := newFreezerMock(t) + err := mockFreezer.Close() + assert.Nil(t, err) + + // Close second time should return error but do not hang sending data to freeze.quit channel + timeout := time.After(1 * time.Second) + errCh := make(chan error) + + go func() { + errCh <- mockFreezer.Close() + }() + + select { + case <-timeout: + t.Fatal("freezer.Close() timed out") + case err := <-errCh: + assert.NotNil(t, err) + assert.Equal(t, "[freezer DB process already stopped]", err.Error()) + } + +} + +// Releaser is an autogenerated mock type for the Releaser type +type releaserMock struct { + mock.Mock +} + +// Release provides a mock function with given fields: +func (_m *releaserMock) Release() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +func newFreezerMock(t *testing.T) *freezer { + mockLock := new(releaserMock) + mockFreezer := &freezer{ + tables: make(map[string]*freezerTable), + instanceLock: mockLock, + quit: make(chan struct{}), + } + mockLock.On("Release").Return(nil) + + started := make(chan bool) + go func() { + started <- true + <-mockFreezer.quit + }() + <-started + + return mockFreezer +} + +// End Quorum diff --git a/core/state/account_extra_data.go b/core/state/account_extra_data.go new file mode 100644 index 0000000000..8e05e8c974 --- /dev/null +++ b/core/state/account_extra_data.go @@ -0,0 +1,160 @@ +// 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 . + +package state + +import ( + "fmt" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/rlp" +) + +// Quorum +// AccountExtraData is to contain extra data that supplements existing Account data. +// It is also maintained in a trie to support rollback. +// Note: +// - update copy() method +// - update DecodeRLP and EncodeRLP when adding new field +type AccountExtraData struct { + // for privacy enhancements + PrivacyMetadata *PrivacyMetadata + // list of public keys managed by the corresponding Tessera. + // This is for multitenancy + ManagedParties []string +} + +func (qmd *AccountExtraData) DecodeRLP(stream *rlp.Stream) error { + var dataRLP struct { + // from state.PrivacyMetadata, this is required to support + // backward compatibility with RLP-encoded state.PrivacyMetadata. + // Refer to rlp/doc.go for decoding rules. + CreationTxHash *common.EncryptedPayloadHash `rlp:"nil"` + // from state.PrivacyMetadata, this is required to support + // backward compatibility with RLP-encoded state.PrivacyMetadata. + // Refer to rlp/doc.go for decoding rules. + PrivacyFlag *engine.PrivacyFlagType `rlp:"nil"` + + Rest []rlp.RawValue `rlp:"tail"` // to maintain forward compatibility + } + if err := stream.Decode(&dataRLP); err != nil { + return err + } + if dataRLP.CreationTxHash != nil && dataRLP.PrivacyFlag != nil { + qmd.PrivacyMetadata = &PrivacyMetadata{ + CreationTxHash: *dataRLP.CreationTxHash, + PrivacyFlag: *dataRLP.PrivacyFlag, + } + } + if len(dataRLP.Rest) > 0 { + var managedParties []string + if err := rlp.DecodeBytes(dataRLP.Rest[0], &managedParties); err != nil { + return fmt.Errorf("fail to decode managedParties with error %v", err) + } + // As RLP encodes empty slice or nil slice as an empty string (192) + // we won't be able to determine when decoding. So we use pragmatic approach + // to default to nil value. Downstream usage would deal with it easier. + if len(managedParties) == 0 { + qmd.ManagedParties = nil + } else { + qmd.ManagedParties = managedParties + } + } + return nil +} + +func (qmd *AccountExtraData) EncodeRLP(writer io.Writer) error { + var ( + hash *common.EncryptedPayloadHash + flag *engine.PrivacyFlagType + ) + if qmd.PrivacyMetadata != nil { + hash = &qmd.PrivacyMetadata.CreationTxHash + flag = &qmd.PrivacyMetadata.PrivacyFlag + } + return rlp.Encode(writer, struct { + CreationTxHash *common.EncryptedPayloadHash `rlp:"nil"` + PrivacyFlag *engine.PrivacyFlagType `rlp:"nil"` + ManagedParties []string + }{ + CreationTxHash: hash, + PrivacyFlag: flag, + ManagedParties: qmd.ManagedParties, + }) +} + +func (qmd *AccountExtraData) copy() *AccountExtraData { + if qmd == nil { + return nil + } + var copyPM *PrivacyMetadata + if qmd.PrivacyMetadata != nil { + copyPM = &PrivacyMetadata{ + CreationTxHash: qmd.PrivacyMetadata.CreationTxHash, + PrivacyFlag: qmd.PrivacyMetadata.PrivacyFlag, + } + } + copyManagedParties := make([]string, len(qmd.ManagedParties)) + copy(copyManagedParties, qmd.ManagedParties) + return &AccountExtraData{ + PrivacyMetadata: copyPM, + ManagedParties: copyManagedParties, + } +} + +// attached to every private contract account +type PrivacyMetadata struct { + CreationTxHash common.EncryptedPayloadHash `json:"creationTxHash"` + PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"` +} + +// Quorum +// privacyMetadataRLP struct is to make sure +// field order is preserved regardless changes in the PrivacyMetadata and its internal +// +// Edit this struct with care to make sure forward and backward compatibility +type privacyMetadataRLP struct { + CreationTxHash common.EncryptedPayloadHash + PrivacyFlag engine.PrivacyFlagType + + Rest []rlp.RawValue `rlp:"tail"` // to maintain forward compatibility +} + +func (p *PrivacyMetadata) DecodeRLP(stream *rlp.Stream) error { + var dataRLP privacyMetadataRLP + if err := stream.Decode(&dataRLP); err != nil { + return err + } + p.CreationTxHash = dataRLP.CreationTxHash + p.PrivacyFlag = dataRLP.PrivacyFlag + return nil +} + +func (p *PrivacyMetadata) EncodeRLP(writer io.Writer) error { + return rlp.Encode(writer, privacyMetadataRLP{ + CreationTxHash: p.CreationTxHash, + PrivacyFlag: p.PrivacyFlag, + }) +} + +func NewStatePrivacyMetadata(creationTxHash common.EncryptedPayloadHash, privacyFlag engine.PrivacyFlagType) *PrivacyMetadata { + return &PrivacyMetadata{ + CreationTxHash: creationTxHash, + PrivacyFlag: privacyFlag, + } +} diff --git a/core/state/account_extra_data_test.go b/core/state/account_extra_data_test.go new file mode 100644 index 0000000000..f158a1c805 --- /dev/null +++ b/core/state/account_extra_data_test.go @@ -0,0 +1,193 @@ +// 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 . + +package state + +import ( + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" +) + +type privacyMetadataOld struct { + CreationTxHash common.EncryptedPayloadHash + PrivacyFlag engine.PrivacyFlagType +} + +// privacyMetadataToBytes is the utility function under test from previous implementation +func privacyMetadataToBytes(pm *privacyMetadataOld) ([]byte, error) { + return rlp.EncodeToBytes(pm) +} + +// bytesToPrivacyMetadata is the utility function under test from previous implementation +func bytesToPrivacyMetadata(b []byte) (*privacyMetadataOld, error) { + var data *privacyMetadataOld + if err := rlp.DecodeBytes(b, &data); err != nil { + return nil, fmt.Errorf("unable to decode privacy metadata. Cause: %v", err) + } + return data, nil +} + +func TestRLP_PrivacyMetadata_DecodeBackwardCompatibility(t *testing.T) { + existingPM := &privacyMetadataOld{ + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("arbitrary-hash")), + PrivacyFlag: engine.PrivacyFlagStateValidation, + } + existing, err := privacyMetadataToBytes(existingPM) + assert.NoError(t, err) + + var actual PrivacyMetadata + err = rlp.DecodeBytes(existing, &actual) + + assert.NoError(t, err, "Must decode PrivacyMetadata successfully") + assert.Equal(t, existingPM.CreationTxHash, actual.CreationTxHash) + assert.Equal(t, existingPM.PrivacyFlag, actual.PrivacyFlag) +} + +func TestRLP_PrivacyMetadata_DecodeForwardCompatibility(t *testing.T) { + pm := &PrivacyMetadata{ + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("arbitrary-hash")), + PrivacyFlag: engine.PrivacyFlagStateValidation, + } + existing, err := rlp.EncodeToBytes(pm) + assert.NoError(t, err) + + var actual *privacyMetadataOld + actual, err = bytesToPrivacyMetadata(existing) + + assert.NoError(t, err, "Must encode PrivacyMetadata successfully") + assert.Equal(t, pm.CreationTxHash, actual.CreationTxHash) + assert.Equal(t, pm.PrivacyFlag, actual.PrivacyFlag) +} + +// From initial privacy enhancements, the privacy metadata is RLP encoded +// we now wrap PrivacyMetadata in a more generic struct. This test is to make sure +// we support backward compatibility. +func TestRLP_AccountExtraData_BackwardCompatibility(t *testing.T) { + // prepare existing RLP bytes + arbitraryExistingMetadata := &PrivacyMetadata{ + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("arbitrary-existing-privacy-metadata-creation-hash")), + PrivacyFlag: engine.PrivacyFlagPartyProtection, + } + existing, err := rlp.EncodeToBytes(arbitraryExistingMetadata) + assert.NoError(t, err) + + // now try to decode with the new struct + var actual AccountExtraData + err = rlp.DecodeBytes(existing, &actual) + + assert.NoError(t, err, "Must decode successfully") + assert.Equal(t, arbitraryExistingMetadata.CreationTxHash, actual.PrivacyMetadata.CreationTxHash) + assert.Equal(t, arbitraryExistingMetadata.PrivacyFlag, actual.PrivacyMetadata.PrivacyFlag) +} + +func TestRLP_AccountExtraData_withField_ManagedParties(t *testing.T) { + // prepare existing RLP bytes + arbitraryExtraData := &AccountExtraData{ + PrivacyMetadata: &PrivacyMetadata{ + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("arbitrary-existing-privacy-metadata-creation-hash")), + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, + ManagedParties: []string{"Arbitrary PK1", "Arbitrary PK2"}, + } + existing, err := rlp.EncodeToBytes(arbitraryExtraData) + assert.NoError(t, err) + + // now try to decode with the new struct + var actual AccountExtraData + err = rlp.DecodeBytes(existing, &actual) + + assert.NoError(t, err, "Must decode successfully") + assert.Equal(t, arbitraryExtraData.PrivacyMetadata.CreationTxHash, actual.PrivacyMetadata.CreationTxHash) + assert.Equal(t, arbitraryExtraData.PrivacyMetadata.PrivacyFlag, actual.PrivacyMetadata.PrivacyFlag) + assert.Equal(t, arbitraryExtraData.ManagedParties, actual.ManagedParties) +} + +func TestRLP_AccountExtraData_whenTypical(t *testing.T) { + expected := AccountExtraData{ + PrivacyMetadata: &PrivacyMetadata{ + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("arbitrary-payload-hash")), + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, + ManagedParties: []string{"XYZ"}, + } + + data, err := rlp.EncodeToBytes(&expected) + assert.NoError(t, err) + + var actual AccountExtraData + assert.NoError(t, rlp.DecodeBytes(data, &actual)) + assert.Equal(t, expected.PrivacyMetadata.CreationTxHash, actual.PrivacyMetadata.CreationTxHash) + assert.Equal(t, expected.PrivacyMetadata.PrivacyFlag, actual.PrivacyMetadata.PrivacyFlag) + assert.Equal(t, expected.ManagedParties, actual.ManagedParties) +} + +func TestRLP_AccountExtraData_whenHavingPrivacyMetadataOnly(t *testing.T) { + expected := AccountExtraData{ + PrivacyMetadata: &PrivacyMetadata{ + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("arbitrary-payload-hash")), + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, + } + + data, err := rlp.EncodeToBytes(&expected) + assert.NoError(t, err) + + var actual AccountExtraData + assert.NoError(t, rlp.DecodeBytes(data, &actual)) + assert.Equal(t, expected.PrivacyMetadata.CreationTxHash, actual.PrivacyMetadata.CreationTxHash) + assert.Equal(t, expected.PrivacyMetadata.PrivacyFlag, actual.PrivacyMetadata.PrivacyFlag) +} + +func TestRLP_AccountExtraData_whenHavingNilManagedParties(t *testing.T) { + expected := AccountExtraData{ + PrivacyMetadata: nil, + ManagedParties: nil, + } + + data, err := rlp.EncodeToBytes(&expected) + assert.NoError(t, err) + + var actual AccountExtraData + assert.NoError(t, rlp.DecodeBytes(data, &actual)) + assert.Nil(t, actual.ManagedParties) + assert.Nil(t, actual.PrivacyMetadata) +} + +func TestRLP_AccountExtraData_whenHavingEmptyManagedParties(t *testing.T) { + expected := AccountExtraData{ + PrivacyMetadata: nil, + ManagedParties: []string{}, + } + + data, err := rlp.EncodeToBytes(&expected) + assert.NoError(t, err) + + var actual AccountExtraData + assert.NoError(t, rlp.DecodeBytes(data, &actual)) + assert.Nil(t, actual.ManagedParties) + assert.Nil(t, actual.PrivacyMetadata) +} + +func TestCopy_whenNil(t *testing.T) { + var testObj *AccountExtraData = nil + + assert.Nil(t, testObj.copy()) +} diff --git a/core/state/database.go b/core/state/database.go index ecc2c134da..32302a47e2 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" lru "github.com/hashicorp/golang-lru" @@ -49,6 +50,12 @@ type Database interface { // TrieDB retrieves the low level trie database used for data storage. TrieDB() *trie.Database + + // Quorum + // + // accountExtraDataLinker maintains mapping between root hash of the state trie + // and root hash of state.AccountExtraData trie. + AccountExtraDataLinker() rawdb.AccountExtraDataLinker } // Trie is a Ethereum Merkle Patricia trie. @@ -109,14 +116,24 @@ func NewDatabase(db ethdb.Database) Database { func NewDatabaseWithCache(db ethdb.Database, cache int) Database { csc, _ := lru.New(codeSizeCacheSize) return &cachingDB{ - db: trie.NewDatabaseWithCache(db, cache), - codeSizeCache: csc, + db: trie.NewDatabaseWithCache(db, cache), + accountExtraDataLinker: rawdb.NewAccountExtraDataLinker(db), + codeSizeCache: csc, } } type cachingDB struct { - db *trie.Database - codeSizeCache *lru.Cache + db *trie.Database + // Quorum + // + // accountExtraDataLinker maintains mapping between state root and state.AccountExtraData root. + // As this struct is the backing store for state, this gives the reference to the linker when needed. + accountExtraDataLinker rawdb.AccountExtraDataLinker + codeSizeCache *lru.Cache +} + +func (db *cachingDB) AccountExtraDataLinker() rawdb.AccountExtraDataLinker { + return db.accountExtraDataLinker } // OpenTrie opens the main account trie at a specific root hash. diff --git a/core/state/dump.go b/core/state/dump.go index 9bb946d14b..911b8cf1d4 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -189,6 +189,36 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool return json } +func (self *StateDB) DumpAddress(address common.Address) (DumpAccount, bool) { + if !self.Exist(address) { + return DumpAccount{}, false + } + + obj := self.getStateObject(address) + account := DumpAccount{ + Balance: obj.data.Balance.String(), + Nonce: obj.data.Nonce, + Root: common.Bytes2Hex(obj.data.Root[:]), + CodeHash: common.Bytes2Hex(obj.data.CodeHash), + Code: common.Bytes2Hex(obj.Code(self.db)), + Storage: make(map[common.Hash]string), + } + + noDataIssue := true + storageIt := trie.NewIterator(obj.getTrie(self.db).NodeIterator(nil)) + for storageIt.Next() { + _, content, _, err := rlp.Split(storageIt.Value) + if err != nil { + noDataIssue = false + log.Error("Failed to decode the value returned by iterator", "error", err) + break + } + account.Storage[common.BytesToHash(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content) + } + + return account, noDataIssue +} + // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) { s.DumpToCollector(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) diff --git a/core/state/journal.go b/core/state/journal.go index f242dac5af..142954788e 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -116,7 +116,11 @@ type ( account *common.Address prevcode, prevhash []byte } - + // Quorum - changes to AccountExtraData + accountExtraDataChange struct { + account *common.Address + prev *AccountExtraData + } // Changes to other state values. refundChange struct { prev uint64 @@ -197,6 +201,17 @@ func (ch codeChange) dirtied() *common.Address { return ch.account } +// Quorum +func (ch accountExtraDataChange) revert(s *StateDB) { + s.getStateObject(*ch.account).setAccountExtraData(ch.prev) +} + +func (ch accountExtraDataChange) dirtied() *common.Address { + return ch.account +} + +// End Quorum - Privacy Enhancements + func (ch storageChange) revert(s *StateDB) { s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 015a673781..133a31f5e9 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -18,9 +18,11 @@ package state import ( "bytes" + "errors" "fmt" "io" "math/big" + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -79,6 +81,13 @@ type stateObject struct { trie Trie // storage trie, which becomes non-nil on first access code Code // contract bytecode, which gets set when code is loaded + // Quorum + // contains extra data that is linked to the account + accountExtraData *AccountExtraData + // as there are many fields in accountExtraData which might be concurrently changed + // this is to make sure we can keep track of changes individually. + accountExtraDataMutex sync.Mutex + originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block dirtyStorage Storage // Storage entries that have been modified in the current transaction execution @@ -90,6 +99,9 @@ type stateObject struct { dirtyCode bool // true if the code was updated suicided bool deleted bool + // Quorum + // flag to track changes in AccountExtraData + dirtyAccountExtraData bool } // empty returns whether the account is considered empty. @@ -167,6 +179,10 @@ func (s *stateObject) getTrie(db Database) Trie { return s.trie } +func (so *stateObject) storageRoot(db Database) common.Hash { + return so.getTrie(db).Hash() +} + // GetState retrieves a value from the account storage trie. func (s *stateObject) GetState(db Database, key common.Hash) common.Hash { // If the fake storage is set, only lookup the state here(in the debugging mode) @@ -426,6 +442,10 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { stateObject.suicided = s.suicided stateObject.dirtyCode = s.dirtyCode stateObject.deleted = s.deleted + // Quorum - copy AccountExtraData + stateObject.accountExtraData = s.accountExtraData + stateObject.dirtyAccountExtraData = s.dirtyAccountExtraData + return stateObject } @@ -499,6 +519,60 @@ func (s *stateObject) setNonce(nonce uint64) { s.data.Nonce = nonce } +// Quorum +// SetAccountExtraData modifies the AccountExtraData reference and journals it +func (s *stateObject) SetAccountExtraData(extraData *AccountExtraData) { + current, _ := s.AccountExtraData() + s.db.journal.append(accountExtraDataChange{ + account: &s.address, + prev: current, + }) + s.setAccountExtraData(extraData) +} + +// A new AccountExtraData will be created if not exists. +// This must be called after successfully acquiring accountExtraDataMutex lock +func (s *stateObject) journalAccountExtraData() *AccountExtraData { + current, _ := s.AccountExtraData() + s.db.journal.append(accountExtraDataChange{ + account: &s.address, + prev: current.copy(), + }) + if current == nil { + current = &AccountExtraData{} + } + return current +} + +// Quorum +// SetStatePrivacyMetadata updates the PrivacyMetadata in AccountExtraData and journals it. +func (s *stateObject) SetStatePrivacyMetadata(pm *PrivacyMetadata) { + s.accountExtraDataMutex.Lock() + defer s.accountExtraDataMutex.Unlock() + + newExtraData := s.journalAccountExtraData() + newExtraData.PrivacyMetadata = pm + s.setAccountExtraData(newExtraData) +} + +// Quorum +// SetStatePrivacyMetadata updates the PrivacyMetadata in AccountExtraData and journals it. +func (s *stateObject) SetManagedParties(managedParties []string) { + s.accountExtraDataMutex.Lock() + defer s.accountExtraDataMutex.Unlock() + + newExtraData := s.journalAccountExtraData() + newExtraData.ManagedParties = managedParties + s.setAccountExtraData(newExtraData) +} + +// Quorum +// setAccountExtraData modifies the AccountExtraData reference in this state object +func (s *stateObject) setAccountExtraData(extraData *AccountExtraData) { + s.accountExtraData = extraData + s.dirtyAccountExtraData = true +} + func (s *stateObject) CodeHash() []byte { return s.data.CodeHash } @@ -511,6 +585,83 @@ func (s *stateObject) Nonce() uint64 { return s.data.Nonce } +// Quorum +// AccountExtraData returns the extra data in this state object. +// It will also update the reference by searching the accountExtraDataTrie. +// +// This method enforces on returning error and never returns (nil, nil). +func (s *stateObject) AccountExtraData() (*AccountExtraData, error) { + if s.accountExtraData != nil { + return s.accountExtraData, nil + } + val, err := s.getCommittedAccountExtraData() + if err != nil { + return nil, err + } + s.accountExtraData = val + return val, nil +} + +// Quorum +// getCommittedAccountExtraData looks for an entry in accountExtraDataTrie. +// +// This method enforces on returning error and never returns (nil, nil). +func (s *stateObject) getCommittedAccountExtraData() (*AccountExtraData, error) { + val, err := s.db.accountExtraDataTrie.TryGet(s.address.Bytes()) + if err != nil { + return nil, fmt.Errorf("unable to retrieve data from the accountExtraDataTrie. Cause: %v", err) + } + if len(val) == 0 { + return nil, fmt.Errorf("%s: %w", s.address.Hex(), common.ErrNoAccountExtraData) + } + var extraData AccountExtraData + if err := rlp.DecodeBytes(val, &extraData); err != nil { + return nil, fmt.Errorf("unable to decode to AccountExtraData. Cause: %v", err) + } + return &extraData, nil +} + +// Quorum - Privacy Enhancements +// PrivacyMetadata returns the reference to PrivacyMetadata. +// It will returrn an error if no PrivacyMetadata is in the AccountExtraData. +func (s *stateObject) PrivacyMetadata() (*PrivacyMetadata, error) { + extraData, err := s.AccountExtraData() + if err != nil { + return nil, err + } + // extraData can't be nil. Refer to s.AccountExtraData() + if extraData.PrivacyMetadata == nil { + return nil, fmt.Errorf("no privacy metadata data for contract %s", s.address.Hex()) + } + return extraData.PrivacyMetadata, nil +} + +func (s *stateObject) GetCommittedPrivacyMetadata() (*PrivacyMetadata, error) { + extraData, err := s.getCommittedAccountExtraData() + if err != nil { + return nil, err + } + if extraData == nil || extraData.PrivacyMetadata == nil { + return nil, fmt.Errorf("The provided contract does not have privacy metadata: %x", s.address) + } + return extraData.PrivacyMetadata, nil +} + +// End Quorum - Privacy Enhancements + +// ManagedParties will return empty if no account extra data found +func (s *stateObject) ManagedParties() ([]string, error) { + extraData, err := s.AccountExtraData() + if errors.Is(err, common.ErrNoAccountExtraData) { + return []string{}, nil + } + if err != nil { + return nil, err + } + // extraData can't be nil. Refer to s.AccountExtraData() + return extraData.ManagedParties, nil +} + // Never called, but must be present to allow stateObject to be used // as a vm.Account interface that also satisfies the vm.ContractRef // interface. Interfaces are awesome. diff --git a/core/state/state_test.go b/core/state/state_test.go index 0dc4c0ad63..cc912bfde4 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -18,6 +18,7 @@ package state import ( "bytes" + "encoding/json" "math/big" "testing" @@ -25,6 +26,8 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" + + checker "gopkg.in/check.v1" ) var toAddr = common.BytesToAddress @@ -87,6 +90,61 @@ func TestDump(t *testing.T) { } } +func (s *stateTest) TestDumpAddress(c *checker.C) { + // generate a few entries + obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) + obj1.AddBalance(big.NewInt(22)) + obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02})) + obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02})) + obj3.SetBalance(big.NewInt(44)) + + // write some of them to the trie + s.state.updateStateObject(obj1) + s.state.updateStateObject(obj2) + s.state.Commit(false) + + addressToDump := toAddr([]byte{0x01}) + // check that dump contains the state objects that are in trie + got, _ := s.state.DumpAddress(addressToDump) + out, _ := json.Marshal(got) + + want := `{"balance":"22","nonce":0,"root":"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"}` + + if string(out) != want { + c.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", string(out), want) + } +} + +func (s *stateTest) TestDumpAddressNotFound(c *checker.C) { + // generate a few entries + obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) + obj1.AddBalance(big.NewInt(22)) + obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02})) + obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02})) + obj3.SetBalance(big.NewInt(44)) + + // write some of them to the trie + s.state.updateStateObject(obj1) + s.state.updateStateObject(obj2) + s.state.Commit(false) + + addressToDump := toAddr([]byte{0x09}) + + // check that dump contains the state objects that are in trie + _, found := s.state.DumpAddress(addressToDump) + + if found { + c.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", found, false) + } +} + +func (s *stateTest) SetUpTest(c *checker.C) { + s.db = rawdb.NewMemoryDatabase() + s.state, _ = New(common.Hash{}, NewDatabase(s.db), nil) +} + func TestNull(t *testing.T) { s := newStateTest() address := common.HexToAddress("0x823140710bf13990e4500136726d8b55") diff --git a/core/state/statedb.go b/core/state/statedb.go index 17dd474314..3db3479a59 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -25,9 +25,11 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" @@ -73,6 +75,9 @@ type StateDB struct { snapAccounts map[common.Hash][]byte snapStorage map[common.Hash]map[common.Hash][]byte + // Quorum - a trie to hold extra account information that cannot be stored in the accounts trie + accountExtraDataTrie Trie + // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*stateObject stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie @@ -121,17 +126,30 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) if err != nil { return nil, err } + + // Quorum - Privacy Enhancements - retrieve the privacy metadata root corresponding to the account state root + extraDataRoot := db.AccountExtraDataLinker().GetAccountExtraDataRoot(root) + log.Debug("Account Extra Data root", "hash", extraDataRoot) + accountExtraDataTrie, err := db.OpenTrie(extraDataRoot) + if err != nil { + return nil, fmt.Errorf("Unable to open privacy metadata trie: %v", err) + } + // End Quorum - Privacy Enhancements + sdb := &StateDB{ - db: db, - trie: tr, - snaps: snaps, - stateObjects: make(map[common.Address]*stateObject), - stateObjectsPending: make(map[common.Address]struct{}), - stateObjectsDirty: make(map[common.Address]struct{}), - logs: make(map[common.Hash][]*types.Log), - preimages: make(map[common.Hash][]byte), - journal: newJournal(), + db: db, + trie: tr, + snaps: snaps, + // Quorum - Privacy Enhancements + accountExtraDataTrie: accountExtraDataTrie, + stateObjects: make(map[common.Address]*stateObject), + stateObjectsPending: make(map[common.Address]struct{}), + stateObjectsDirty: make(map[common.Address]struct{}), + logs: make(map[common.Hash][]*types.Log), + preimages: make(map[common.Hash][]byte), + journal: newJournal(), } + if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { sdb.snapDestructs = make(map[common.Hash]struct{}) @@ -142,6 +160,20 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } +// Quorum +// NewDual - Create a public and private state from a given public and private tree +func NewDual(root common.Hash, db Database, snaps *snapshot.Tree, ethDb ethdb.Database, privateDb Database, privateSnaps *snapshot.Tree) (state, privateState *StateDB, err error) { + state, err = New(root, db, snaps) + if err != nil { + return nil, nil, err + } + privateState, err = New(rawdb.GetPrivateStateRoot(ethDb, root), privateDb, privateSnaps) + if err != nil { + return nil, nil, err + } + return state, privateState, nil +} + // setError remembers the first non-nil error it is called with. func (s *StateDB) setError(err error) { if s.dbErr == nil { @@ -268,6 +300,45 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 { return 0 } +func (s *StateDB) GetPrivacyMetadata(addr common.Address) (*PrivacyMetadata, error) { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.PrivacyMetadata() + } + return nil, nil +} + +func (s *StateDB) GetCommittedStatePrivacyMetadata(addr common.Address) (*PrivacyMetadata, error) { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.GetCommittedPrivacyMetadata() + } + return nil, nil +} + +func (self *StateDB) GetManagedParties(addr common.Address) ([]string, error) { + stateObject := self.getStateObject(addr) + if stateObject != nil { + return stateObject.ManagedParties() + } + return nil, nil +} + +func (s *StateDB) GetRLPEncodedStateObject(addr common.Address) ([]byte, error) { + stateObject := s.getStateObject(addr) + if stateObject == nil { + return nil, fmt.Errorf("no state found for %s", addr.Hex()) + } + // When calculating the execution hash or simulating the transaction the stateOject state is not committed/updated + // In order to reflect the updated state invoke stateObject.updateRoot on a copy of the state object. + if len(stateObject.pendingStorage) > 0 || len(stateObject.dirtyStorage) > 0 || stateObject.dirtyCode { + cpy := stateObject.deepCopy(s) + cpy.updateRoot(s.db) + return rlp.EncodeToBytes(cpy) + } + return rlp.EncodeToBytes(stateObject) +} + // TxIndex returns the current transaction index set by Prepare. func (s *StateDB) TxIndex() int { return s.txIndex @@ -363,6 +434,16 @@ func (s *StateDB) HasSuicided(addr common.Address) bool { return false } +// Quorum +// GetStorageRoot returns the root of the storage associated with the given address. +func (s *StateDB) GetStorageRoot(addr common.Address) (common.Hash, error) { + so := s.getStateObject(addr) + if so == nil { + return common.Hash{}, fmt.Errorf("can't find state object") + } + return so.storageRoot(s.db), nil +} + /* * SETTERS */ @@ -397,6 +478,20 @@ func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { } } +func (s *StateDB) SetPrivacyMetadata(addr common.Address, metadata *PrivacyMetadata) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetStatePrivacyMetadata(metadata) + } +} + +func (self *StateDB) SetManagedParties(addr common.Address, managedParties []string) { + stateObject := self.GetOrNewStateObject(addr) + if stateObject != nil && len(managedParties) > 0 { + stateObject.SetManagedParties(managedParties) + } +} + func (s *StateDB) SetCode(addr common.Address, code []byte) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { @@ -446,6 +541,8 @@ func (s *StateDB) Suicide(addr common.Address) bool { // // updateStateObject writes the given object to the trie. +// Quorum: +// - update AccountExtraData trie func (s *StateDB) updateStateObject(obj *stateObject) { // Track the amount of time wasted on updating the account from the trie if metrics.EnabledExpensive { @@ -469,9 +566,29 @@ func (s *StateDB) updateStateObject(obj *stateObject) { if s.snap != nil { s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) } + + // Quorum - Privacy Enhancements - update the privacy metadata trie in case the privacy metadata is dirty + if err != nil { + return + } + + if obj.dirtyAccountExtraData && obj.accountExtraData != nil { + extraDataBytes, err := rlp.EncodeToBytes(obj.accountExtraData) + if err != nil { + panic(fmt.Errorf("can't encode privacy metadata at %x: %v", addr[:], err)) + } + err = s.accountExtraDataTrie.TryUpdate(addr[:], extraDataBytes) + if err != nil { + s.setError(err) + return + } + } + } // deleteStateObject removes the given object from the state trie. +// Quorum: +// - delete the data from the extra data trie corresponding to the account address func (s *StateDB) deleteStateObject(obj *stateObject) { // Track the amount of time wasted on deleting the account from the trie if metrics.EnabledExpensive { @@ -481,7 +598,9 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { addr := obj.Address() if err := s.trie.TryDelete(addr[:]); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) + return } + s.setError(s.accountExtraDataTrie.TryDelete(addr[:])) } // getStateObject retrieves a state object given by the address, returning nil if @@ -643,16 +762,18 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common func (s *StateDB) Copy() *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ - db: s.db, - trie: s.db.CopyTrie(s.trie), - stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), - stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), - stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), - refund: s.refund, - logs: make(map[common.Hash][]*types.Log, len(s.logs)), - logSize: s.logSize, - preimages: make(map[common.Hash][]byte, len(s.preimages)), - journal: newJournal(), + db: s.db, + trie: s.db.CopyTrie(s.trie), + // Quorum - Privacy Enhancements + accountExtraDataTrie: s.db.CopyTrie(s.accountExtraDataTrie), + stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), + stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), + stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), + refund: s.refund, + logs: make(map[common.Hash][]*types.Log, len(s.logs)), + logSize: s.logSize, + preimages: make(map[common.Hash][]byte, len(s.preimages)), + journal: newJournal(), } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -808,6 +929,8 @@ func (s *StateDB) clearJournalAndRefund() { } // Commit writes the state to the underlying in-memory trie database. +// Quorum: +// - linking state root and the AccountExtraData root func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if s.dbErr != nil { return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) @@ -837,8 +960,6 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if metrics.EnabledExpensive { start = time.Now() } - // The onleaf func is called _serially_, so we can reuse the same account - // for unmarshalling every time. var account Account root, err := s.trie.Commit(func(leaf []byte, parent common.Hash) error { if err := rlp.DecodeBytes(leaf, &account); err != nil { @@ -872,5 +993,24 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil } + + // Quorum + // linking the state root and the AccountExtraData root + if err == nil { + // commit the AccountExtraData trie + extraDataRoot, err := s.accountExtraDataTrie.Commit(nil) + if err != nil { + return common.Hash{}, fmt.Errorf("unable to commit the AccountExtraData trie: %v", err) + } + log.Debug("AccountExtraData root after trie commit", "root", extraDataRoot) + // link the new state root to the AccountExtraData root + err = s.db.AccountExtraDataLinker().Link(root, extraDataRoot) + if err != nil { + return common.Hash{}, fmt.Errorf("Unable to link the state root to the privacy metadata root: %v", err) + } + // add a reference from the AccountExtraData root to the state root so that when the state root is written + // to the DB the the AccountExtraData root is also written + s.db.TrieDB().Reference(extraDataRoot, root) + } return root, err } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 824a597498..ddfe467e64 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -29,6 +29,8 @@ import ( "testing" "testing/quick" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -144,6 +146,37 @@ func TestIntermediateLeaks(t *testing.T) { } } +func TestStorageRoot(t *testing.T) { + var ( + mem = rawdb.NewMemoryDatabase() + db = NewDatabase(mem) + state, _ = New(common.Hash{}, db, nil) + addr = common.Address{1} + key = common.Hash{1} + value = common.Hash{42} + + empty = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + ) + + so := state.GetOrNewStateObject(addr) + + emptyRoot := so.storageRoot(db) + if emptyRoot != empty { + t.Errorf("Invalid empty storage root, expected %x, got %x", empty, emptyRoot) + } + + // add a bit of state + so.SetState(db, key, value) + state.Commit(false) + + root := so.storageRoot(db) + expected := common.HexToHash("63511abd258fa907afa30cb118b53744a4f49055bb3f531da512c6b866fc2ffb") + + if expected != root { + t.Errorf("Invalid storage root, expected %x, got %x", expected, root) + } +} + // TestCopy tests that copying a statedb object indeed makes the original and // the copy independent of each other. This test is a regression test against // https://github.com/ethereum/go-ethereum/pull/15549. @@ -290,6 +323,22 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { }, args: make([]int64, 2), }, + { + name: "SetPrivacyMetadata", + fn: func(a testAction, s *StateDB) { + + privFlag := engine.PrivacyFlagType((uint64(a.args[0])%2)*2 + 1) // the only possible values should be 1 and 3 + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(a.args[1])) + hash := common.BytesToEncryptedPayloadHash(b) + + s.SetPrivacyMetadata(addr, &PrivacyMetadata{ + CreationTxHash: hash, + PrivacyFlag: privFlag, + }) + }, + args: make([]int64, 2), + }, { name: "CreateAccount", fn: func(a testAction, s *StateDB) { @@ -431,6 +480,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr)) checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr)) checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr)) + statePM, _ := state.GetPrivacyMetadata(addr) + checkStatePM, _ := checkstate.GetPrivacyMetadata(addr) + checkeq("GetPrivacyMetadata", statePM, checkStatePM) // Check storage. if obj := state.getStateObject(addr); obj != nil { state.ForEachStorage(addr, func(key, value common.Hash) bool { @@ -727,3 +779,206 @@ func TestMissingTrieNodes(t *testing.T) { t.Fatalf("expected error, got root :%x", root) } } + +// Quorum - NewDual + +func TestStorageRootNewDual(t *testing.T) { + + var ( + pubMem = rawdb.NewMemoryDatabase() + privMem = rawdb.NewMemoryDatabase() + pubDb = NewDatabase(pubMem) + privDb = NewDatabase(privMem) + pubState, privState, _ = NewDual(common.Hash{}, pubDb, nil, privMem, privDb, nil) + addr = common.Address{1} + key = common.Hash{1} + value = common.Hash{42} + + empty = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + ) + + args := []struct { + db Database + state *StateDB + }{ + { + pubDb, pubState, + }, + { + privDb, privState, + }, + } + + for _, arg := range args { + so := arg.state.GetOrNewStateObject(addr) + + emptyRoot := so.storageRoot(arg.db) + if emptyRoot != empty { + t.Errorf("Invalid empty storage root, expected %x, got %x", empty, emptyRoot) + } + + // add a bit of state + so.SetState(arg.db, key, value) + arg.state.Commit(false) + + root := so.storageRoot(arg.db) + expected := common.HexToHash("63511abd258fa907afa30cb118b53744a4f49055bb3f531da512c6b866fc2ffb") + + if expected != root { + t.Errorf("Invalid storage root, expected %x, got %x", expected, root) + } + } +} + +// End Quorum - NewDual + +// Quorum - Privacy Enhancements +func TestPrivacyMetadataIsSavedOnStateDbCommit(t *testing.T) { + ethDb := rawdb.NewMemoryDatabase() + stateDb := NewDatabase(ethDb) + state, _ := New(common.Hash{}, stateDb, nil) + + addr := common.Address{1} + state.CreateAccount(addr) + + state.SetNonce(addr, uint64(1)) + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + CreationTxHash: common.EncryptedPayloadHash{1}, + }) + + privMetaData, _ := state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData != nil { + t.Errorf("privacy metadata should not have been stored before commit") + } + + state.Commit(false) + + privMetaData, _ = state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData == nil { + t.Errorf("privacy metadata should have been stored during commit") + } +} + +func TestPrivacyMetadataIsUpdatedOnAccountReCreateWithDifferentPrivacyMetadata(t *testing.T) { + ethDb := rawdb.NewMemoryDatabase() + stateDb := NewDatabase(ethDb) + state, _ := New(common.Hash{}, stateDb, nil) + + addr := common.Address{1} + state.CreateAccount(addr) + + state.SetNonce(addr, uint64(1)) + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + CreationTxHash: common.EncryptedPayloadHash{1}, + }) + state.Commit(false) + + privMetaData, _ := state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData == nil { + t.Errorf("privacy metadata should have been stored during commit") + } + + state.CreateAccount(addr) + state.SetNonce(addr, uint64(1)) + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStateValidation, + CreationTxHash: common.EncryptedPayloadHash{1}, + }) + + state.Commit(false) + + privMetaData, _ = state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData == nil { + t.Errorf("privacy metadata should have been updated during commit") + } else if privMetaData.PrivacyFlag != engine.PrivacyFlagStateValidation { + t.Errorf("privacy metadata should have StateValidation as the the privacy flag") + } +} + +func TestPrivacyMetadataIsRemovedOnAccountSuicide(t *testing.T) { + ethDb := rawdb.NewMemoryDatabase() + stateDb := NewDatabase(ethDb) + state, _ := New(common.Hash{}, stateDb, nil) + + addr := common.Address{1} + state.CreateAccount(addr) + + state.SetNonce(addr, uint64(1)) + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + CreationTxHash: common.EncryptedPayloadHash{1}, + }) + state.Commit(false) + + privMetaData, _ := state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData == nil { + t.Errorf("privacy metadata should have been stored during commit") + } + + state.Suicide(addr) + state.Commit(false) + + privMetaData, _ = state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData != nil { + t.Errorf("privacy metadata should have been deleted during account suicide") + } +} + +func TestPrivacyMetadataChangesAreRolledBackOnRevert(t *testing.T) { + ethDb := rawdb.NewMemoryDatabase() + stateDb := NewDatabase(ethDb) + state, _ := New(common.Hash{}, stateDb, nil) + + addr := common.Address{1} + state.CreateAccount(addr) + + state.SetNonce(addr, uint64(1)) + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("one")), + }) + state.Commit(false) + + privMetaData, _ := state.GetCommittedStatePrivacyMetadata(addr) + if privMetaData == nil { + t.Errorf("privacy metadata should have been stored during commit") + } + + // update privacy metadata + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStateValidation, + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("two")), + }) + + // record the snapshot + snapshot := state.Snapshot() + + privMetaData, _ = state.GetPrivacyMetadata(addr) + if privMetaData.CreationTxHash != common.BytesToEncryptedPayloadHash([]byte("two")) { + t.Errorf("current privacy metadata creation tx hash does not match the expected value") + } + + // update the metadata + state.SetPrivacyMetadata(addr, &PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStateValidation, + CreationTxHash: common.BytesToEncryptedPayloadHash([]byte("three")), + }) + + privMetaData, _ = state.GetPrivacyMetadata(addr) + if privMetaData.CreationTxHash != common.BytesToEncryptedPayloadHash([]byte("three")) { + t.Errorf("current privacy metadata creation tx hash does not match the expected value") + } + + // revert to snapshot + state.RevertToSnapshot(snapshot) + + privMetaData, _ = state.GetPrivacyMetadata(addr) + if privMetaData.CreationTxHash != common.BytesToEncryptedPayloadHash([]byte("two")) { + t.Errorf("current privacy metadata creation tx hash does not match the expected value") + } + +} + +// End Quorum - Privacy Enhancements diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 1c550fa8bc..8605b38f5e 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -48,7 +48,7 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse // Prefetch processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb, but any changes are discarded. The // only goal is to pre-cache transaction signatures and state trie nodes. -func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *uint32) { +func (p *statePrefetcher) Prefetch(block *types.Block, statedb, privatest *state.StateDB, cfg vm.Config, interrupt *uint32) { var ( header = block.Header() gaspool = new(GasPool).AddGas(block.GasLimit()) @@ -62,7 +62,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c } // Block precaching permitted to continue, execute the transaction statedb.Prepare(tx.Hash(), block.Hash(), i) - if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, header, tx, cfg); err != nil { + if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, privatest, header, tx, cfg); err != nil { return // Ugh, something went horribly wrong, bail out } // If we're pre-byzantium, pre-load trie nodes for the intermediate root @@ -79,7 +79,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error { +func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool *GasPool, statedb *state.StateDB, privatest *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error { // Convert the transaction into an executable message and pre-cache its sender msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { @@ -87,7 +87,12 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co } // Create the EVM and execute the transaction context := NewEVMContext(msg, header, bc, author) - vm := vm.NewEVM(context, statedb, config, cfg) + // Quorum decide on the privatestateDB to use for EVM + privateStateDbToUse := PrivateStateDBForTxn(config.IsQuorum, tx.IsPrivate(), statedb, privatest) + + vm := vm.NewEVM(context, statedb, privateStateDbToUse, config, cfg) + vm.SetCurrentTX(tx) + // /Quorum _, err = ApplyMessage(vm, msg, gaspool) return err diff --git a/core/state_processor.go b/core/state_processor.go index e655d8f3bf..a631b5621b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/permission/core" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -53,13 +54,16 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *StateProcessor) Process(block *types.Block, statedb, privateState *state.StateDB, cfg vm.Config) (types.Receipts, types.Receipts, []*types.Log, uint64, error) { + var ( receipts types.Receipts usedGas = new(uint64) header = block.Header() allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) + + privateReceipts types.Receipts ) // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { @@ -68,37 +72,75 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Iterate over and process the individual transactions for i, tx := range block.Transactions() { statedb.Prepare(tx.Hash(), block.Hash(), i) - receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg) + privateState.Prepare(tx.Hash(), block.Hash(), i) + + receipt, privateReceipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, privateState, header, tx, usedGas, cfg) if err != nil { - return nil, nil, 0, err + return nil, nil, nil, 0, err } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) + + // if the private receipt is nil this means the tx was public + // and we do not need to apply the additional logic. + if privateReceipt != nil { + privateReceipts = append(privateReceipts, privateReceipt) + allLogs = append(allLogs, privateReceipt.Logs...) + p.bc.CheckAndSetPrivateState(privateReceipt.Logs, privateState) + } } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) - return receipts, allLogs, *usedGas, nil + return receipts, privateReceipts, allLogs, *usedGas, nil } +// Quorum +// returns the privateStateDB to be used for a transaction +func PrivateStateDBForTxn(isQuorum, isPrivate bool, stateDb, privateStateDB *state.StateDB) *state.StateDB { + if !isQuorum || !isPrivate { + return stateDb + } + return privateStateDB +} + +// /Quorum + // ApplyTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb, privateState *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *types.Receipt, error) { + // Quorum - decide the privateStateDB to use + privateStateDbToUse := PrivateStateDBForTxn(config.IsQuorum, tx.IsPrivate(), statedb, privateState) + // /Quorum + + // Quorum - check for account permissions to execute the transaction + if core.IsV2Permission() { + if err := core.CheckAccountPermission(tx.From(), tx.To(), tx.Value(), tx.Data(), tx.Gas(), tx.GasPrice()); err != nil { + return nil, nil, err + } + } + + if config.IsQuorum && tx.GasPrice() != nil && tx.GasPrice().Cmp(common.Big0) > 0 { + return nil, nil, ErrInvalidGasPrice + } + msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { - return nil, err + return nil, nil, err } // Create a new context to be used in the EVM environment context := NewEVMContext(msg, header, bc, author) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(context, statedb, config, cfg) + vmenv := vm.NewEVM(context, statedb, privateStateDbToUse, config, cfg) + vmenv.SetCurrentTX(tx) + // Apply the transaction to the current state (included in the env) result, err := ApplyMessage(vmenv, msg, gp) if err != nil { - return nil, err + return nil, nil, err } // Update the state with pending changes var root []byte @@ -109,9 +151,13 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo } *usedGas += result.UsedGas + // If this is a private transaction, the public receipt should always + // indicate success. + publicFailed := !(config.IsQuorum && tx.IsPrivate()) && result.Failed() + // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx - // based on the eip phase, we're passing whether the root touch-delete accounts. - receipt := types.NewReceipt(root, result.Failed(), *usedGas) + // based on the eip phase, we're passing wether the root touch-delete accounts. + receipt := types.NewReceipt(root, publicFailed, *usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas // if the transaction created a contract, store the creation address in the receipt. @@ -125,5 +171,24 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo receipt.BlockNumber = header.Number receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, err + var privateReceipt *types.Receipt + if config.IsQuorum && tx.IsPrivate() { + var privateRoot []byte + if config.IsByzantium(header.Number) { + privateState.Finalise(true) + } else { + privateRoot = privateState.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() + } + privateReceipt = types.NewReceipt(privateRoot, result.Failed(), *usedGas) + privateReceipt.TxHash = tx.Hash() + privateReceipt.GasUsed = result.UsedGas + if msg.To() == nil { + privateReceipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) + } + + privateReceipt.Logs = privateState.GetLogs(tx.Hash()) + privateReceipt.Bloom = types.CreateBloom(types.Receipts{privateReceipt}) + } + + return receipt, privateReceipt, err } diff --git a/core/state_transition.go b/core/state_transition.go index 9a9bf475e9..eebedc2cc7 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,12 +17,19 @@ package core import ( + "errors" "math" "math/big" + "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/private" ) /* @@ -103,6 +110,12 @@ func (result *ExecutionResult) Revert() []byte { return common.CopyBytes(result.ReturnData) } +// PrivateMessage implements a private message +type PrivateMessage interface { + Message + IsPrivate() bool +} + // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) { // Set the starting gas for the raw transaction @@ -149,7 +162,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition gasPrice: msg.GasPrice(), value: msg.Value(), data: msg.Data(), - state: evm.StateDB, + state: evm.PublicState(), } } @@ -160,6 +173,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. + func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) { return NewStateTransition(evm, msg, gp).TransitionDb() } @@ -213,6 +227,15 @@ func (st *StateTransition) preCheck() error { // // However if any consensus issue encountered, return the error directly with // nil evm execution result. +// +// Quorum: +// 1. Intrinsic gas is calculated based on the encrypted payload hash +// and NOT the actual private payload +// 2. For private transactions, we only deduct intrinsic gas from the gas pool +// regardless the current node is party to the transaction or not +// 3. With multitenancy support, we enforce the party set in the contract index must contain all +// parties from the transaction. This is to detect unauthorized access from a legit proxy contract +// to an unauthorized contract. func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // First check this message satisfies all consensus rules before // applying the message. The rules include these clauses @@ -225,7 +248,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // 6. caller has enough balance to cover asset transfer for **topmost** call // Check clauses 1-3, buy gas if everything is correct - if err := st.preCheck(); err != nil { + var err error + if err = st.preCheck(); err != nil { return nil, err } msg := st.msg @@ -233,7 +257,50 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber) istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber) contractCreation := msg.To() == nil + isQuorum := st.evm.ChainConfig().IsQuorum + snapshot := st.evm.StateDB.Snapshot() + var data []byte + var managedPartiesInTx []string + isPrivate := false + publicState := st.state + pmh := newPMH(st) + if msg, ok := msg.(PrivateMessage); ok && isQuorum && msg.IsPrivate() { + isPrivate = true + pmh.snapshot = snapshot + pmh.eph = common.BytesToEncryptedPayloadHash(st.data) + _, managedPartiesInTx, data, pmh.receivedPrivacyMetadata, err = private.P.Receive(pmh.eph) + // Increment the public account nonce if: + // 1. Tx is private and *not* a participant of the group and either call or create + // 2. Tx is private we are part of the group and is a call + if err != nil || !contractCreation { + publicState.SetNonce(sender.Address(), publicState.GetNonce(sender.Address())+1) + } + if err != nil { + return &ExecutionResult{ + UsedGas: 0, + Err: nil, + ReturnData: nil, + }, nil + } + + pmh.hasPrivatePayload = data != nil + + vmErr, consensusErr := pmh.prepare() + if consensusErr != nil || vmErr != nil { + return &ExecutionResult{ + UsedGas: 0, + Err: vmErr, + ReturnData: nil, + }, consensusErr + } + } else { + data = st.data + } + + // Pay intrinsic gas. For a private contract this is done using the public hash passed in, + // not the private data retrieved above. This is because we need any (participant) validator + // node to get the same result as a (non-participant) minter node, to avoid out-of-gas issues. // Check clauses 4-5, subtract intrinsic gas if everything is correct gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul) if err != nil { @@ -249,19 +316,132 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, ErrInsufficientFundsForTransfer } var ( - ret []byte - vmerr error // vm errors do not effect consensus and are therefore not assigned to err + leftoverGas uint64 + evm = st.evm + ret []byte + // vm errors do not effect consensus and are therefor + // not assigned to err, except for insufficient balance + // error. + vmerr error ) if contractCreation { - ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value) + ret, _, leftoverGas, vmerr = evm.Create(sender, data, st.gas, st.value) } else { - // Increment the nonce for the next transaction - st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) - ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) + // Increment the account nonce only if the transaction isn't private. + // If the transaction is private it has already been incremented on + // the public state. + if !isPrivate { + publicState.SetNonce(msg.From(), publicState.GetNonce(sender.Address())+1) + } + var to common.Address + if isQuorum { + to = *st.msg.To() + } else { + to = st.to() + } + //if input is empty for the smart contract call, return + if len(data) == 0 && isPrivate { + st.refundGas() + st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) + return &ExecutionResult{ + UsedGas: 0, + Err: nil, + ReturnData: nil, + }, nil + } + + ret, leftoverGas, vmerr = evm.Call(sender, to, data, st.gas, st.value) + } + if vmerr != nil { + log.Info("VM returned with error", "err", vmerr) + // The only possible consensus-error would be if there wasn't + // sufficient balance to make the transfer happen. The first + // balance transfer may never fail. + if vmerr == vm.ErrInsufficientBalance { + return nil, vmerr + } + if errors.Is(vmerr, multitenancy.ErrNotAuthorized) { + return nil, vmerr + } + } + + // Quorum - Privacy Enhancements + // perform privacy enhancements checks + if pmh.mustVerify() { + var exitEarly bool + exitEarly, err = pmh.verify(vmerr) + if exitEarly { + return &ExecutionResult{ + UsedGas: 0, + Err: ErrPrivateContractInteractionVerificationFailed, + ReturnData: nil, + }, err + } + } + // End Quorum - Privacy Enhancements + + // Quorum + // do the affected contract managed party checks + if msg, ok := msg.(PrivateMessage); ok && isQuorum && st.evm.SupportsMultitenancy && msg.IsPrivate() { + if len(managedPartiesInTx) > 0 { + for _, address := range evm.AffectedContracts() { + managedPartiesInContract, err := st.evm.StateDB.GetManagedParties(address) + if err != nil { + return nil, err + } + // managed parties for public transactions is empty so nothing to check there + if len(managedPartiesInContract) > 0 { + if common.NotContainsAll(managedPartiesInContract, managedPartiesInTx) { + log.Debug("Managed parties check has failed for contract", "addr", address, "EPH", + pmh.eph.TerminalString(), "contractMP", managedPartiesInContract, "txMP", managedPartiesInTx) + st.evm.RevertToSnapshot(snapshot) + // TODO - see whether we can find a way to store this error and make it available via customizations to getTransactionReceipt + return &ExecutionResult{ + UsedGas: 0, + Err: ErrContractManagedPartiesCheckFailed, + ReturnData: nil, + }, nil + } + } + } + } } + + // Pay gas used during contract creation or execution (st.gas tracks remaining gas) + // However, if private contract then we don't want to do this else we can get + // a mismatch between a (non-participant) minter and (participant) validator, + // which can cause a 'BAD BLOCK' crash. + if !isPrivate { + st.gas = leftoverGas + } + // End Quorum + st.refundGas() st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) + // Quorum + // for all contracts being created as the result of the transaction execution + // we build the index for them if multitenancy is enabled + if st.evm.SupportsMultitenancy { + addresses := evm.CreatedContracts() + for _, address := range addresses { + log.Debug("Save to extra data", + "address", strings.ToLower(address.Hex()), + "isPrivate", isPrivate, + "parties", managedPartiesInTx) + st.evm.StateDB.SetManagedParties(address, managedPartiesInTx) + } + } + + if isPrivate { + return &ExecutionResult{ + UsedGas: 0, + Err: vmerr, + ReturnData: ret, + }, err + } + // End Quorum + return &ExecutionResult{ UsedGas: st.gasUsed(), Err: vmerr, @@ -290,3 +470,25 @@ func (st *StateTransition) refundGas() { func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gas } + +// Quorum - Privacy Enhancements - implement the pmcStateTransitionAPI interface +func (st *StateTransition) SetTxPrivacyMetadata(pm *types.PrivacyMetadata) { + st.evm.SetTxPrivacyMetadata(pm) +} +func (st *StateTransition) IsPrivacyEnhancementsEnabled() bool { + return st.evm.ChainConfig().IsPrivacyEnhancementsEnabled(st.evm.BlockNumber) +} +func (st *StateTransition) RevertToSnapshot(snapshot int) { + st.evm.StateDB.RevertToSnapshot(snapshot) +} +func (st *StateTransition) GetStatePrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + return st.evm.StateDB.GetPrivacyMetadata(addr) +} +func (st *StateTransition) CalculateMerkleRoot() (common.Hash, error) { + return st.evm.CalculateMerkleRoot() +} +func (st *StateTransition) AffectedContracts() []common.Address { + return st.evm.AffectedContracts() +} + +// End Quorum - Privacy Enhancements diff --git a/core/state_transition_pmh.go b/core/state_transition_pmh.go new file mode 100644 index 0000000000..d7a0079ba4 --- /dev/null +++ b/core/state_transition_pmh.go @@ -0,0 +1,136 @@ +package core + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private/engine" +) + +type pmcStateTransitionAPI interface { + SetTxPrivacyMetadata(pm *types.PrivacyMetadata) + IsPrivacyEnhancementsEnabled() bool + RevertToSnapshot(int) + GetStatePrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) + CalculateMerkleRoot() (common.Hash, error) + AffectedContracts() []common.Address +} + +func newPMH(st pmcStateTransitionAPI) *privateMessageHandler { + return &privateMessageHandler{stAPI: st} +} + +type privateMessageHandler struct { + stAPI pmcStateTransitionAPI + + hasPrivatePayload bool + + snapshot int + receivedPrivacyMetadata *engine.ExtraMetadata + eph common.EncryptedPayloadHash +} + +func (pmh *privateMessageHandler) mustVerify() bool { + return pmh.hasPrivatePayload && pmh.receivedPrivacyMetadata != nil && pmh.stAPI.IsPrivacyEnhancementsEnabled() +} + +// checks the privacy metadata in the state transition context +// returns vmError if there is an error in the EVM execution +// returns consensusErr if there is an error in the consensus execution +func (pmh *privateMessageHandler) prepare() (vmError, consensusErr error) { + if pmh.receivedPrivacyMetadata != nil { + if !pmh.stAPI.IsPrivacyEnhancementsEnabled() && pmh.receivedPrivacyMetadata.PrivacyFlag.IsNotStandardPrivate() { + // This situation is only possible if the current node has been upgraded (both quorum and tessera) yet the + // node did not apply the privacyEnhancementsBlock configuration (with a network agreed block height). + // Since this would be considered node misconfiguration the behavior should be changed to return an error + // which would then cause the node not to apply the block (and potentially get stuck and not be able to + // continue to apply new blocks). The resolution should then be to revert to an appropriate block height and + // run geth init with the network agreed privacyEnhancementsBlock. + // The prepare method signature has been changed to allow returning the relevant error. + return ErrPrivacyEnhancedReceivedWhenDisabled, fmt.Errorf("Privacy enhanced transaction received while privacy enhancements are disabled."+ + " Please check your node configuration. EPH=%s", pmh.eph.ToBase64()) + } + + if pmh.receivedPrivacyMetadata.PrivacyFlag == engine.PrivacyFlagStateValidation && common.EmptyHash(pmh.receivedPrivacyMetadata.ACMerkleRoot) { + log.Error(ErrPrivacyMetadataInvalidMerkleRoot.Error()) + return ErrPrivacyMetadataInvalidMerkleRoot, nil + } + privMetadata := types.NewTxPrivacyMetadata(pmh.receivedPrivacyMetadata.PrivacyFlag) + pmh.stAPI.SetTxPrivacyMetadata(privMetadata) + } + return nil, nil +} + +//If the list of affected CA Transactions by the time evm executes is different from the list of affected contract transactions returned from Tessera +//an Error should be thrown and the state should not be updated +//This validation is to prevent cases where the list of affected contract will have changed by the time the evm actually executes transaction +// failed = true will make sure receipt is marked as "failure" +// return error will crash the node and only use when that's the case +func (pmh *privateMessageHandler) verify(vmerr error) (bool, error) { + // convenient function to return error. It has the same signature as the main function + returnErrorFunc := func(anError error, logMsg string, ctx ...interface{}) (exitEarly bool, err error) { + if logMsg != "" { + log.Debug(logMsg, ctx...) + } + pmh.stAPI.RevertToSnapshot(pmh.snapshot) + exitEarly = true + if anError != nil { + err = fmt.Errorf("vmerr=%s, err=%s", vmerr, anError) + } + return + } + actualACAddresses := pmh.stAPI.AffectedContracts() + log.Trace("Verify hashes of affected contracts", "expectedHashes", pmh.receivedPrivacyMetadata.ACHashes, "numberOfAffectedAddresses", len(actualACAddresses)) + privacyFlag := pmh.receivedPrivacyMetadata.PrivacyFlag + for _, addr := range actualACAddresses { + // GetPrivacyMetadata is invoked on the privateState (as the tx is private) and it returns: + // 1. public contacts: privacyMetadata = nil, err = nil + // 2. private contracts of type: + // 2.1. StandardPrivate: privacyMetadata = nil, err = "The provided contract does not have privacy metadata" + // 2.2. PartyProtection/PSV: privacyMetadata = , err = nil + actualPrivacyMetadata, err := pmh.stAPI.GetStatePrivacyMetadata(addr) + //when privacyMetadata should have been recovered but wasnt (includes non-party) + //non party will only be caught here if sender provides privacyFlag + if err != nil && privacyFlag.IsNotStandardPrivate() { + return returnErrorFunc(nil, "Unable to find PrivacyMetadata for affected contract", "err", err, "addr", addr.Hex()) + } + log.Trace("Privacy metadata", "affectedAddress", addr.Hex(), "metadata", actualPrivacyMetadata) + // both public and standard private contracts will be nil and can be skipped in acoth check + // public contracts - evm error for write, no error for reads + // standard private - only error if privacyFlag sent with tx or if no flag sent but other affecteds have privacyFlag + if actualPrivacyMetadata == nil { + continue + } + // Check that the affected contracts privacy flag matches the transaction privacy flag. + // I know that this is also checked by tessera, but it only checks for non standard private transactions. + if actualPrivacyMetadata.PrivacyFlag != pmh.receivedPrivacyMetadata.PrivacyFlag { + return returnErrorFunc(nil, "Mismatched privacy flags", + "affectedContract.Address", addr.Hex(), + "affectedContract.PrivacyFlag", actualPrivacyMetadata.PrivacyFlag, + "received.PrivacyFlag", pmh.receivedPrivacyMetadata.PrivacyFlag) + } + // acoth check - case where node isn't privy to one of actual affecteds + if pmh.receivedPrivacyMetadata.ACHashes.NotExist(actualPrivacyMetadata.CreationTxHash) { + return returnErrorFunc(nil, "Participation check failed", + "affectedContractAddress", addr.Hex(), + "missingCreationTxHash", actualPrivacyMetadata.CreationTxHash.Hex()) + } + } + + // check the psv merkle root comparison - for both creation and msg calls + if !common.EmptyHash(pmh.receivedPrivacyMetadata.ACMerkleRoot) { + log.Trace("Verify merkle root", "merkleRoot", pmh.receivedPrivacyMetadata.ACMerkleRoot) + actualACMerkleRoot, err := pmh.stAPI.CalculateMerkleRoot() + if err != nil { + return returnErrorFunc(err, "") + } + if actualACMerkleRoot != pmh.receivedPrivacyMetadata.ACMerkleRoot { + return returnErrorFunc(nil, "Merkle Root check failed", "actual", actualACMerkleRoot, + "expect", pmh.receivedPrivacyMetadata.ACMerkleRoot) + } + } + return false, nil +} diff --git a/core/state_transition_pmh_test.go b/core/state_transition_pmh_test.go new file mode 100644 index 0000000000..34cfd2e164 --- /dev/null +++ b/core/state_transition_pmh_test.go @@ -0,0 +1,53 @@ +package core + +import ( + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/private/engine" + testifyassert "github.com/stretchr/testify/assert" +) + +type stubPmhStateTransition struct { + snapshot int +} + +func (s *stubPmhStateTransition) SetTxPrivacyMetadata(pm *types.PrivacyMetadata) { +} + +func (s *stubPmhStateTransition) IsPrivacyEnhancementsEnabled() bool { + return true +} + +func (s *stubPmhStateTransition) RevertToSnapshot(val int) { + s.snapshot = val +} + +func (s *stubPmhStateTransition) GetStatePrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + return &state.PrivacyMetadata{PrivacyFlag: engine.PrivacyFlagStateValidation, CreationTxHash: common.EncryptedPayloadHash{1}}, nil +} + +func (s *stubPmhStateTransition) CalculateMerkleRoot() (common.Hash, error) { + return common.Hash{}, fmt.Errorf("Unable to calculate MerkleRoot") +} + +func (s *stubPmhStateTransition) AffectedContracts() []common.Address { + return make([]common.Address, 0) +} + +func TestPrivateMessageContextVerify_WithMerkleRootCreationError(t *testing.T) { + assert := testifyassert.New(t) + stateTransitionAPI := &stubPmhStateTransition{} + + pmc := newPMH(stateTransitionAPI) + pmc.receivedPrivacyMetadata = &engine.ExtraMetadata{ACMerkleRoot: common.Hash{1}, PrivacyFlag: engine.PrivacyFlagStateValidation} + pmc.snapshot = 10 + exitEarly, err := pmc.verify(nil) + + assert.Error(err, "verify must return an error due to the MerkleRoot calculation error") + assert.Equal(pmc.snapshot, stateTransitionAPI.snapshot, "Revert should have been called") + assert.True(exitEarly, "Exit early should be true") +} diff --git a/core/state_transition_test.go b/core/state_transition_test.go new file mode 100644 index 0000000000..8c8e9a89f2 --- /dev/null +++ b/core/state_transition_test.go @@ -0,0 +1,1363 @@ +package core + +import ( + "fmt" + "math/big" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/private/engine/notinuse" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + testifyassert "github.com/stretchr/testify/assert" +) + +var ( + c1 = &contract{ + name: "c1", + abi: mustParse(c1AbiDefinition), + bytecode: common.Hex2Bytes("608060405234801561001057600080fd5b506040516020806105a88339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610550806100586000396000f3fe608060405260043610610051576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b1146100565780636d4ce63c146100a5578063d7139463146100d0575b600080fd5b34801561006257600080fd5b5061008f6004803603602081101561007957600080fd5b810190808035906020019092919050505061010b565b6040518082815260200191505060405180910390f35b3480156100b157600080fd5b506100ba61011e565b6040518082815260200191505060405180910390f35b3480156100dc57600080fd5b50610109600480360360208110156100f357600080fd5b8101908080359060200190929190505050610127565b005b6000816000819055506000549050919050565b60008054905090565b600030610132610212565b808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050604051809103906000f080158015610184573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff166360fe47b1836040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101f657600080fd5b505af115801561020a573d6000803e3d6000fd5b505050505050565b604051610302806102238339019056fe608060405234801561001057600080fd5b506040516020806103028339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610271806100916000396000f3fe608060405260043610610046576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b11461004b5780636d4ce63c14610086575b600080fd5b34801561005757600080fd5b506100846004803603602081101561006e57600080fd5b81019080803590602001909291905050506100b1565b005b34801561009257600080fd5b5061009b610180565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166360fe47b1826040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b15801561014157600080fd5b505af1158015610155573d6000803e3d6000fd5b505050506040513d602081101561016b57600080fd5b81019080805190602001909291905050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636d4ce63c6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801561020557600080fd5b505afa158015610219573d6000803e3d6000fd5b505050506040513d602081101561022f57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820a537f4c360ce5c6f55523298e314e6456e5c3e02c170563751dfda37d3aeddb30029a165627a7a7230582060396bfff29d2dfc5a9f4216bfba5e24d031d54fd4b26ebebde1a26c59df0c1e0029"), + } + c2 = &contract{ + name: "c2", + abi: mustParse(c2AbiDefinition), + bytecode: common.Hex2Bytes("608060405234801561001057600080fd5b506040516020806102f58339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610264806100916000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b1146100585780636d4ce63c14610086575b600080fd5b6100846004803603602081101561006e57600080fd5b81019080803590602001909291905050506100a4565b005b61008e610173565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166360fe47b1826040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b15801561013457600080fd5b505af1158015610148573d6000803e3d6000fd5b505050506040513d602081101561015e57600080fd5b81019080805190602001909291905050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636d4ce63c6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156101f857600080fd5b505afa15801561020c573d6000803e3d6000fd5b505050506040513d602081101561022257600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820dd8a5dcf693e1969289c444a282d0684a9760bac26f1e4e0139d46821ec1979b0029"), + } + + // exec hash helper vars (accounts/tries) + signingAddress = common.StringToAddress("contract") + + c1AccAddress = crypto.CreateAddress(signingAddress, 0) + c2AccAddress = crypto.CreateAddress(signingAddress, 1) + + // this is used as the field key in account storage (which is the index/sequence of the field in the contract) + // both contracts have only one field (c1 - has the value while c2 has c1's address) + // For more info please see: https://solidity.readthedocs.io/en/v0.6.8/internals/layout_in_storage.html + firstFieldKey = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") + + val42 = common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000002A") + val53 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000035") + valC1Address = append(common.Hex2Bytes("000000000000000000000000"), c1AccAddress.Bytes()...) + + // this is the contract storage trie after storing value 42 + c1StorageTrieWithValue42 = secureTrieWithStoredValue(firstFieldKey, val42) + c1StorageTrieWithValue53 = secureTrieWithStoredValue(firstFieldKey, val53) + c2StorageTrieWithC1Address = secureTrieWithStoredValue(firstFieldKey, valC1Address) + + // The contract bytecode above includes the constructor bytecode (which is removed by the EVM before storing the + // contract bytecode) thus it can't be used to calculate the code hash for the contract. + // Below we deploy both of them as public contracts and extract the resulting codeHashes from the public state. + c1CodeHash, c2CodeHash = contractCodeHashes() + + c1AccountWithValue42Stored = &state.Account{Nonce: 1, Balance: big.NewInt(0), Root: c1StorageTrieWithValue42.Hash(), CodeHash: c1CodeHash.Bytes()} + c1AccountWithValue53Stored = &state.Account{Nonce: 1, Balance: big.NewInt(0), Root: c1StorageTrieWithValue53.Hash(), CodeHash: c1CodeHash.Bytes()} + c2AccountWithC1AddressStored = &state.Account{Nonce: 1, Balance: big.NewInt(0), Root: c2StorageTrieWithC1Address.Hash(), CodeHash: c2CodeHash.Bytes()} +) + +type contract struct { + abi abi.ABI + bytecode []byte + name string +} + +func (c *contract) create(args ...interface{}) []byte { + bytes, err := c.abi.Pack("", args...) + if err != nil { + panic("can't pack: " + err.Error()) + } + return append(c.bytecode, bytes...) +} + +func (c *contract) set(value int64) []byte { + bytes, err := c.abi.Pack("set", big.NewInt(value)) + if err != nil { + panic("can't pack: " + err.Error()) + } + return bytes +} + +func (c *contract) get() []byte { + bytes, err := c.abi.Pack("get") + if err != nil { + panic("can't pack: " + err.Error()) + } + return bytes +} + +func init() { + log.PrintOrigins(true) + log.Root().SetHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(true))) +} + +func secureTrieWithStoredValue(key []byte, value []byte) *trie.SecureTrie { + res, _ := trie.NewSecure(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) + res.Update(key, v) + return res +} + +func contractCodeHashes() (c1CodeHash common.Hash, c2CodeHash common.Hash) { + assert := testifyassert.New(nil) + cfg := newConfig() + + // create public c1 + cfg.setData(c1.create(big.NewInt(42))) + c1Address := createPublicContract(cfg, assert, c1) + c1CodeHash = cfg.publicState.GetCodeHash(c1Address) + + // create public c2 + cfg.setNonce(1) + cfg.setData(c2.create(c1Address)) + c2Address := createPublicContract(cfg, assert, c2) + c2CodeHash = cfg.publicState.GetCodeHash(c2Address) + + return +} + +func TestApplyMessage_Private_whenTypicalCreate_Success(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + + // calling C1.Create standard private + cfg := newConfig(). + setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData([]byte("arbitrary encrypted payload hash")) + gp := new(GasPool).AddGas(math.MaxUint64) + privateMsg := newTypicalPrivateMessage(cfg) + + //since standard private create only get back PrivacyFlag + mockPM.When("Receive").Return(c1.create(big.NewInt(42)), &engine.ExtraMetadata{ + PrivacyFlag: engine.PrivacyFlagStandardPrivate, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, gp) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenCreatePartyProtectionC1_Success(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + + // calling C1.Create party protection + cfg := newConfig(). + setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary encrypted payload hash")) + gp := new(GasPool).AddGas(math.MaxUint64) + privateMsg := newTypicalPrivateMessage(cfg) + + //since party protection create only get back privacyFlag + mockPM.When("Receive").Return(c1.create(big.NewInt(42)), &engine.ExtraMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, gp) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenCreatePartyProtectionC1WithPrivacyEnhancementsDisabledReturnsError(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + + // calling C1.Create party protection + cfg := newConfig(). + setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary encrypted payload hash")) + + gp := new(GasPool).AddGas(math.MaxUint64) + privateMsg := newTypicalPrivateMessage(cfg) + + //since party protection create only get back privacyFlag + mockPM.When("Receive").Return(c1.create(big.NewInt(42)), &engine.ExtraMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + evm := newEVM(cfg) + evm.ChainConfig().PrivacyEnhancementsBlock = nil + result, err := ApplyMessage(evm, privateMsg, gp) + + assert.Error(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + // check that there is no privacy metadata for the newly created contract + assert.Len(evm.CreatedContracts(), 0, "no contracts created") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenInteractWithPartyProtectionC1_Success(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + //create party protection c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // calling C1.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(1). + setTo(c1Address) + privateMsg := newTypicalPrivateMessage(cfg) + //since party protection need ACHashes and PrivacyFlag + mockPM.When("Receive").Return(c1.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenInteractWithStateValidationC1_Success(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + //create state validation c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData(c1EncPayloadHash) + cfg.acMerkleRoot, _ = calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue42Stored}) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // calling C1.Set() state validation + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData([]byte("arbitrary enc payload hash")). + setNonce(1). + setTo(c1Address) + privateMsg := newTypicalPrivateMessage(cfg) + mr, _ := calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue53Stored}) + //since state validation need ACHashes, MerkleRoot and PrivacyFlag + mockPM.When("Receive").Return(c1.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagStateValidation, + ACMerkleRoot: mr, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenInteractWithStateValidationC1WithEmptyMRFromTessera_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create state validation c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData(c1EncPayloadHash) + cfg.acMerkleRoot, _ = calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue42Stored}) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // calling C1.Set() state validation + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData([]byte("arbitrary enc payload hash")). + setNonce(1). + setTo(c1Address) + privateMsg := newTypicalPrivateMessage(cfg) + // since state validation need ACHashes, privacyFlag, MerkleRoot + mockPM.When("Receive").Return(c1.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagStateValidation, + ACMerkleRoot: common.Hash{}, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenInteractWithStateValidationC1WithWrongMRFromTessera_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + //create state validation c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData(c1EncPayloadHash) + cfg.acMerkleRoot, _ = calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue42Stored}) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // calling C1.Set() state validation + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData([]byte("arbitrary enc payload hash")). + setNonce(1). + setTo(c1Address) + privateMsg := newTypicalPrivateMessage(cfg) + //since state validation need ACHashes, PrivacyFlag, MerkleRoot + mockPM.When("Receive").Return(c1.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagStateValidation, + ACMerkleRoot: common.Hash{123}, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +//Limitation of design --if don't send privacyFlag can't be guaranteed to catch non-party +//review this... +func TestApplyMessage_Private_whenNonPartyTriesInteractingWithPartyProtectionC1_NoFlag_Succeed(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + //act like doesnt exist on non-party node + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // calling C1.Set() + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData([]byte("arbitrary enc payload hash")). + setNonce(1). + setTo(c1Address) + privateMsg := newTypicalPrivateMessage(cfg) + //will have no ACHashes because when non-party sends tx, because no flag it doesn't generate privacyMetadata info + //actual execution will find affected contract, but non-party won't have info + mockPM.When("Receive").Return(c1.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{}, + PrivacyFlag: engine.PrivacyFlagStandardPrivate, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenNonPartyTriesInteractingWithPartyProtectionC1_WithFlag_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + //act like doesnt exist on non-party node + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // calling C1.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData([]byte("arbitrary enc payload hash")). + setNonce(1). + setTo(c1Address) + privateMsg := newTypicalPrivateMessage(cfg) + mockPM.When("Receive").Return(c1.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +// C1 is a existing contract before privacy enhancements implementation +func TestApplyMessage_Private_whenPartyProtectionC2InteractsExistingStandardPrivateC1_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create c1 like c1 already exist before privacy enhancements + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(math.MaxUint64). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create party protection c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + // since party protection need ACHashes (only private non standard) and PrivacyFlag + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenPartyProtectionC2InteractsNewStandardPrivateC1_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create default standard private c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create party protection c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + // since party protection need ACHashes (only private non standard) and PrivacyFlag + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenPartyProtectionC2InteractsWithPartyProtectionC1_Succeed(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create party protection c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create party protection c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + // since party protection need ACHashes and PrivacyFlag + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +//scenario where sender Q1 runs simulation which affects c2 and c1 privy for Q3 and Q7 +//Q3 receives block but wasn't privy to C1 so doesn't have creation info in tessera +func TestApplyMessage_Private_whenPartyProtectionC2AndC1ButMissingC1CreationInTessera_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create c1 as a party protection + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create party protection c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + // since party protection need ACHashes and PrivacyFlag + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +//scenario where the simulation is run on the Q1 (privatefor Q3 and Q7) and 3 contracts are affected (C2,C1,C0) +//but now Q3 receives block and should be privy to all 3 given tessera response +//but doesn't have C0 privacyMetadata stored in its db +// UPDATE - after relaxing the ACOTH checks this is a valid scenario where C0 acoth is ignored if it isn't detected as an +// affected contract during transaction execution +func TestApplyMessage_Private_whenPartyProtectionC2AndC1AndC0ButMissingC0InStateDB_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create party protection c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create party protection c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + c3EncPayloadHash := []byte("c3") + // calling C2.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + // since party protection need ACHashes and PrivacyFlag + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + common.BytesToEncryptedPayloadHash(c3EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + // after ACOTH check updates this is a successful scenario + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenStateValidationC2InteractsWithStateValidationC1_Succeed(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create party protection c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData(c1EncPayloadHash) + cfg.acMerkleRoot, _ = calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue42Stored}) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create state validation c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData(c2EncPayloadHash). + setNonce(1) + cfg.acMerkleRoot, _ = calcAccMR(accEntry{address: c2AccAddress, account: c2AccountWithC1AddressStored}) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() state validation + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + + stuff := crypto.Keccak256Hash(c2.bytecode) + log.Trace("stuff", "c2code", stuff[:]) + + privateMsg := newTypicalPrivateMessage(cfg) + mr, _ := calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue53Stored}, accEntry{address: c2AccAddress, account: c2AccountWithC1AddressStored}) + //since state validation need ACHashes, PrivacyFlag & MerkleRoot + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagStateValidation, + ACMerkleRoot: mr, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenStateValidationC2InteractsWithPartyProtectionC1_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create party protection c1 + c1EncPayloadHash := []byte("c1") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c1EncPayloadHash) + c1Address := createContract(cfg, mockPM, assert, c1, big.NewInt(42)) + + // create state validation c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData(c2EncPayloadHash). + setNonce(1) + cfg.acMerkleRoot, _ = calcAccMR(accEntry{address: c2AccAddress, account: c2AccountWithC1AddressStored}) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() state validation + cfg.setPrivacyFlag(engine.PrivacyFlagStateValidation). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + // use the correctly calculated MR so that it can't be a source of false positives + mr, _ := calcAccMR(accEntry{address: c1AccAddress, account: c1AccountWithValue53Stored}, accEntry{address: c2AccAddress, account: c2AccountWithC1AddressStored}) + //since state validation need ACHashes, PrivacyFlag & MerkleRoot + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + common.BytesToEncryptedPayloadHash(c1EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagStateValidation, + ACMerkleRoot: mr, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenStandardPrivateC2InteractsWithPublicC1_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create public c1 + cfg.setData(c1.create(big.NewInt(42))) + c1Address := createPublicContract(cfg, assert, c1) + + // create standard private c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() standard private + cfg.setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + //since standard private call no ACHashes, no MerkleRoot + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{}, + PrivacyFlag: engine.PrivacyFlagStandardPrivate, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenPartyProtectionC2InteractsWithPublicC1_Fail(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + cfg := newConfig() + + // create public c1 + cfg.setData(c1.create(big.NewInt(42))) + c1Address := createPublicContract(cfg, assert, c1) + + // create party protection c2 + c2EncPayloadHash := []byte("c2") + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData(c2EncPayloadHash). + setNonce(1) + c2Address := createContract(cfg, mockPM, assert, c2, c1Address) + + // calling C2.Set() party protection + cfg.setPrivacyFlag(engine.PrivacyFlagPartyProtection). + setData([]byte("arbitrary enc payload hash")). + setNonce(2). + setTo(c2Address) + privateMsg := newTypicalPrivateMessage(cfg) + mockPM.When("Receive").Return(c2.set(53), &engine.ExtraMetadata{ + ACHashes: common.EncryptedPayloadHashes{ + common.BytesToEncryptedPayloadHash(c2EncPayloadHash): struct{}{}, + }, + PrivacyFlag: engine.PrivacyFlagPartyProtection, + }, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "EVM execution") + assert.True(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenTxManagerReturnsError_Success(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + + // calling C1.Create standard private + cfg := newConfig(). + setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData([]byte("arbitrary encrypted payload hash")) + gp := new(GasPool).AddGas(math.MaxUint64) + privateMsg := newTypicalPrivateMessage(cfg) + + //since standard private create only get back PrivacyFlag + mockPM.When("Receive").Return(nil, nil, fmt.Errorf("Error during receive")) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, gp) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func TestApplyMessage_Private_whenTxManagerReturnsEmptyResult_Success(t *testing.T) { + originalP := private.P + defer func() { private.P = originalP }() + mockPM := newMockPrivateTransactionManager() + private.P = mockPM + assert := testifyassert.New(t) + + // calling C1.Create standard private + cfg := newConfig(). + setPrivacyFlag(engine.PrivacyFlagStandardPrivate). + setData([]byte("arbitrary encrypted payload hash")) + gp := new(GasPool).AddGas(math.MaxUint64) + privateMsg := newTypicalPrivateMessage(cfg) + + //since standard private create only get back PrivacyFlag + mockPM.When("Receive").Return(nil, nil, nil) + + result, err := ApplyMessage(newEVM(cfg), privateMsg, gp) + + assert.NoError(err, "EVM execution") + assert.False(result.Failed(), "Transaction receipt status") + mockPM.Verify(assert) +} + +func createContract(cfg *config, mockPM *mockPrivateTransactionManager, assert *testifyassert.Assertions, c *contract, args ...interface{}) common.Address { + defer mockPM.reset() + + privateMsg := newTypicalPrivateMessage(cfg) + metadata := &engine.ExtraMetadata{} + if cfg.privacyFlag < math.MaxUint64 { + metadata.PrivacyFlag = cfg.privacyFlag + if metadata.PrivacyFlag == engine.PrivacyFlagStateValidation { + metadata.ACMerkleRoot = cfg.acMerkleRoot + } + } + mockPM.When("Receive").Return(c.create(args...), metadata, nil) + + evm := newEVM(cfg) + result, err := ApplyMessage(evm, privateMsg, new(GasPool).AddGas(math.MaxUint64)) + + assert.NoError(err, "%s: EVM execution", c.name) + assert.False(result.Failed(), "%s: Transaction receipt status", c.name) + mockPM.Verify(assert) + createdContracts := evm.CreatedContracts() + log.Trace("priv statedb", "evmstatedb", evm.StateDB) + assert.Len(createdContracts, 1, "%s: Number of created contracts", c.name) + address := createdContracts[0] + log.Debug("Created "+c.name, "address", address) + return address +} + +func createPublicContract(cfg *config, assert *testifyassert.Assertions, c *contract) common.Address { + pubcfg := cfg.setPublicToPrivateState() + msg := newTypicalPublicMessage(pubcfg) + + evm := newEVM(pubcfg) + result, err := ApplyMessage(evm, msg, new(GasPool).AddGas(math.MaxUint64)) + assert.NoError(err, "%s: EVM execution", c.name) + assert.False(result.Failed(), "%s: Transaction receipt status", c.name) + createdContracts := evm.CreatedContracts() + log.Trace("pub statedb", "evmstatedb", evm.StateDB) + assert.Len(createdContracts, 1, "%s: Number of created contracts", c.name) + address := createdContracts[0] + log.Debug("Created "+c.name, "address", address) + return address +} + +func newTypicalPrivateMessage(cfg *config) PrivateMessage { + var tx *types.Transaction + if cfg.to == nil { + tx = types.NewContractCreation(cfg.nonce, big.NewInt(0), math.MaxUint64, big.NewInt(0), cfg.data) + } else { + tx = types.NewTransaction(cfg.nonce, *cfg.to, big.NewInt(0), math.MaxUint64, big.NewInt(0), cfg.data) + } + tx.SetPrivate() + if cfg.privacyFlag < math.MaxUint64 { + tx.SetTxPrivacyMetadata(&types.PrivacyMetadata{ + PrivacyFlag: cfg.privacyFlag, + }) + } else { + tx.SetTxPrivacyMetadata(nil) // simulate standard private transaction + } + msg, err := tx.AsMessage(&stubSigner{}) + if err != nil { + panic(fmt.Sprintf("can't create a new private message: %s", err)) + } + cfg.currentTx = tx + return PrivateMessage(msg) +} + +func newTypicalPublicMessage(cfg *config) Message { + var tx *types.Transaction + if cfg.to == nil { + tx = types.NewContractCreation(cfg.nonce, big.NewInt(0), math.MaxUint64, big.NewInt(0), cfg.data) + } else { + tx = types.NewTransaction(cfg.nonce, *cfg.to, big.NewInt(0), math.MaxUint64, big.NewInt(0), cfg.data) + } + tx.SetTxPrivacyMetadata(nil) + msg, err := tx.AsMessage(&stubSigner{}) + if err != nil { + panic(fmt.Sprintf("can't create a new private message: %s", err)) + } + cfg.currentTx = tx + return msg +} + +type accEntry struct { + address common.Address + account *state.Account +} + +func calcAccMR(entries ...accEntry) (common.Hash, error) { + combined := new(trie.Trie) + for _, entry := range entries { + data, err := rlp.EncodeToBytes(entry.account) + if err != nil { + return common.Hash{}, err + } + if err = combined.TryUpdate(entry.address.Bytes(), data); err != nil { + return common.Hash{}, err + } + } + return combined.Hash(), nil +} + +type config struct { + from common.Address + to *common.Address + data []byte + nonce uint64 + + privacyFlag engine.PrivacyFlagType + acMerkleRoot common.Hash + + currentTx *types.Transaction + + publicState, privateState *state.StateDB +} + +func newConfig() *config { + pubDatabase := rawdb.NewMemoryDatabase() + privDatabase := rawdb.NewMemoryDatabase() + publicState, _ := state.New(common.Hash{}, state.NewDatabase(pubDatabase), nil) + privateState, _ := state.New(common.Hash{}, state.NewDatabase(privDatabase), nil) + return &config{ + privateState: privateState, + publicState: publicState, + } +} + +func (cfg config) setPublicToPrivateState() *config { + cfg.privateState = cfg.publicState + return &cfg +} + +func (cfg *config) setPrivacyFlag(f engine.PrivacyFlagType) *config { + cfg.privacyFlag = f + return cfg +} + +func (cfg *config) setData(bytes []byte) *config { + cfg.data = bytes + return cfg +} + +func (cfg *config) setNonce(n uint64) *config { + cfg.nonce = n + return cfg +} + +func (cfg *config) setTo(address common.Address) *config { + cfg.to = &address + return cfg +} + +func newEVM(cfg *config) *vm.EVM { + context := vm.Context{ + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: func(uint64) common.Hash { return common.Hash{} }, + + Origin: common.Address{}, + Coinbase: common.Address{}, + BlockNumber: new(big.Int), + Time: big.NewInt(time.Now().Unix()), + Difficulty: new(big.Int), + GasLimit: uint64(3450366), + GasPrice: big.NewInt(0), + } + evm := vm.NewEVM(context, cfg.publicState, cfg.privateState, ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + ByzantiumBlock: new(big.Int), + HomesteadBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, + EIP150Block: new(big.Int), + EIP155Block: new(big.Int), + EIP158Block: new(big.Int), + IsQuorum: true, + PrivacyEnhancementsBlock: new(big.Int), + }, vm.Config{}) + evm.SetCurrentTX(cfg.currentTx) + return evm +} + +func mustParse(def string) abi.ABI { + ret, err := abi.JSON(strings.NewReader(def)) + if err != nil { + panic(fmt.Sprintf("Can't parse ABI def %s", err)) + } + return ret +} + +type stubSigner struct { +} + +func (ss *stubSigner) Sender(tx *types.Transaction) (common.Address, error) { + return signingAddress, nil +} + +func (ss *stubSigner) SignatureValues(tx *types.Transaction, sig []byte) (r, s, v *big.Int, err error) { + panic("implement me") +} + +func (ss *stubSigner) Hash(tx *types.Transaction) common.Hash { + panic("implement me") +} + +func (ss *stubSigner) Equal(types.Signer) bool { + panic("implement me") +} + +type mockPrivateTransactionManager struct { + notinuse.PrivateTransactionManager + returns map[string][]interface{} + currentMethod string + count map[string]int +} + +func (mpm *mockPrivateTransactionManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return true +} + +func (mpm *mockPrivateTransactionManager) Receive(data common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) { + mpm.count["Receive"]++ + values := mpm.returns["Receive"] + var ( + r1 []byte + r2 *engine.ExtraMetadata + r3 error + ) + if values[0] != nil { + r1 = values[0].([]byte) + } + if values[1] != nil { + r2 = values[1].(*engine.ExtraMetadata) + } + if values[2] != nil { + r3 = values[2].(error) + } + return "", nil, r1, r2, r3 +} + +func (mpm *mockPrivateTransactionManager) When(name string) *mockPrivateTransactionManager { + mpm.currentMethod = name + mpm.count[name] = -1 + return mpm +} + +func (mpm *mockPrivateTransactionManager) Return(values ...interface{}) { + mpm.returns[mpm.currentMethod] = values +} + +func (mpm *mockPrivateTransactionManager) Verify(assert *testifyassert.Assertions) { + for m, c := range mpm.count { + assert.True(c > -1, "%s has not been called", m) + } +} + +func (mpm *mockPrivateTransactionManager) reset() { + mpm.count = make(map[string]int) + mpm.currentMethod = "" + mpm.returns = make(map[string][]interface{}) +} + +func newMockPrivateTransactionManager() *mockPrivateTransactionManager { + return &mockPrivateTransactionManager{ + returns: make(map[string][]interface{}), + count: make(map[string]int), + } +} + +const ( + c1AbiDefinition = ` +[ + { + "constant": false, + "inputs": [ + { + "name": "newValue", + "type": "uint256" + } + ], + "name": "set", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newValue", + "type": "uint256" + } + ], + "name": "newContractC2", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "initVal", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } +] +` + c2AbiDefinition = ` +[ + { + "constant": false, + "inputs": [ + { + "name": "_val", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "result", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "_t", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } +] +` +) + +func verifyGasPoolCalculation(t *testing.T, pm private.PrivateTransactionManager) { + assert := testifyassert.New(t) + saved := private.P + defer func() { + private.P = saved + }() + private.P = pm + + txGasLimit := uint64(100000) + gasPool := new(GasPool).AddGas(200000) + // this payload would give us 25288 intrinsic gas + arbitraryEncryptedPayload := "4ab80888354582b92ab442a317828386e4bf21ea4a38d1a9183fbb715f199475269d7686939017f4a6b28310d5003ebd8e012eade530b79e157657ce8dd9692a" + expectedGasPool := new(GasPool).AddGas(177988) // only intrinsic gas is deducted + + db := rawdb.NewMemoryDatabase() + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + msg := privateCallMsg{ + callmsg: callmsg{ + addr: common.Address{2}, + to: &common.Address{}, + value: new(big.Int), + gas: txGasLimit, + gasPrice: big.NewInt(0), + data: common.Hex2Bytes(arbitraryEncryptedPayload), + }, + } + ctx := NewEVMContext(msg, &dualStateTestHeader, nil, &common.Address{}) + evm := vm.NewEVM(ctx, publicState, privateState, params.QuorumTestChainConfig, vm.Config{}) + + tx := types.NewTransaction( + 0, + common.Address{}, + big.NewInt(0), + txGasLimit, + big.NewInt(0), + common.Hex2Bytes(arbitraryEncryptedPayload)) + evm.SetCurrentTX(tx) + + arbitraryBalance := big.NewInt(100000000) + publicState.SetBalance(evm.Coinbase, arbitraryBalance) + publicState.SetBalance(msg.From(), arbitraryBalance) + + testObject := NewStateTransition(evm, msg, gasPool) + + result, err := testObject.TransitionDb() + + assert.NoError(err) + assert.False(result.Failed()) + + assert.Equal(new(big.Int).SetUint64(expectedGasPool.Gas()), new(big.Int).SetUint64(gasPool.Gas()), "gas pool must be calculated correctly") + assert.Equal(arbitraryBalance, publicState.GetBalance(evm.Coinbase), "balance must not be changed") + assert.Equal(arbitraryBalance, publicState.GetBalance(msg.From()), "balance must not be changed") +} + +func TestStateTransition_TransitionDb_GasPoolCalculation_whenNonPartyNodeProcessingPrivateTransactions(t *testing.T) { + stubPTM := &StubPrivateTransactionManager{ + responses: map[string][]interface{}{ + "Receive": { + []byte{}, + nil, + }, + }, + } + verifyGasPoolCalculation(t, stubPTM) +} + +func TestStateTransition_TransitionDb_GasPoolCalculation_whenPartyNodeProcessingPrivateTransactions(t *testing.T) { + stubPTM := &StubPrivateTransactionManager{ + responses: map[string][]interface{}{ + "Receive": { + common.Hex2Bytes("600a6000526001601ff300"), + nil, + }, + }, + } + verifyGasPoolCalculation(t, stubPTM) +} + +type privateCallMsg struct { + callmsg +} + +func (pm privateCallMsg) IsPrivate() bool { return true } + +type StubPrivateTransactionManager struct { + notinuse.PrivateTransactionManager + responses map[string][]interface{} +} + +func (spm *StubPrivateTransactionManager) Receive(data common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) { + res := spm.responses["Receive"] + if err, ok := res[1].(error); ok { + return "", nil, nil, nil, err + } + if ret, ok := res[0].([]byte); ok { + return "", nil, ret, &engine.ExtraMetadata{ + PrivacyFlag: engine.PrivacyFlagStandardPrivate, + }, nil + } + return "", nil, nil, nil, nil +} + +func (spm *StubPrivateTransactionManager) ReceiveRaw(hash common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) { + _, sender, data, metadata, err := spm.Receive(hash) + return data, sender[0], metadata, err +} + +func (spm *StubPrivateTransactionManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return true +} diff --git a/core/tx_pool.go b/core/tx_pool.go index 1636cc5066..784dd1fd14 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + pcore "github.com/ethereum/go-ethereum/permission/core" ) const ( @@ -48,7 +49,8 @@ const ( // non-trivial consequences: larger transactions are significantly harder and // more expensive to propagate; larger transactions also take more resources // to validate whether they fit into the pool or not. - txMaxSize = 4 * txSlotSize // 128KB + // txMaxSize = 4 * txSlotSize // 128KB + // Quorum - value above is not used. instead, ChainConfig.TransactionSizeLimit is used ) var ( @@ -79,6 +81,12 @@ var ( // than some meaningful limit a user might use. This is not a consensus error // making the transaction invalid, rather a DOS protection. ErrOversizedData = errors.New("oversized data") + + ErrInvalidGasPrice = errors.New("Gas price not 0") + + // ErrEtherValueUnsupported is returned if a transaction specifies an Ether Value + // for a private Quorum transaction. + ErrEtherValueUnsupported = errors.New("ether value is not supported for private transactions") ) var ( @@ -126,7 +134,7 @@ const ( type blockChain interface { CurrentBlock() *types.Block GetBlock(hash common.Hash, number uint64) *types.Block - StateAt(root common.Hash) (*state.StateDB, error) + StateAt(root common.Hash) (*state.StateDB, *state.StateDB, error) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription } @@ -147,6 +155,10 @@ type TxPoolConfig struct { GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts Lifetime time.Duration // Maximum amount of time non-executable transaction are queued + + // Quorum + TransactionSizeLimit uint64 // Maximum size allowed for valid transaction (in KB) + MaxCodeSize uint64 // Maximum size allowed of contract code that can be deployed (in KB) } // DefaultTxPoolConfig contains the default configurations for the transaction @@ -164,6 +176,10 @@ var DefaultTxPoolConfig = TxPoolConfig{ GlobalQueue: 1024, Lifetime: 3 * time.Hour, + + // Quorum + TransactionSizeLimit: 64, + MaxCodeSize: 24, } // sanitize checks the provided user configurations and changes anything that's @@ -514,10 +530,17 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { - // Reject transactions over defined size to prevent DOS attacks - if uint64(tx.Size()) > txMaxSize { + // Quorum + sizeLimit := pool.chainconfig.TransactionSizeLimit + if sizeLimit == 0 { + sizeLimit = DefaultTxPoolConfig.TransactionSizeLimit + } + // Reject transactions over 64KB (or manually set limit) to prevent DOS attacks + if float64(tx.Size()) > float64(sizeLimit*1024) { return ErrOversizedData } + // /Quorum + // Transactions can't be negative. This may never happen using RLP decoded // transactions but may occur if you create a transaction using the RPC. if tx.Value().Sign() < 0 { @@ -532,10 +555,26 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if err != nil { return ErrInvalidSender } - // Drop non-local transactions under our own minimal accepted gas price - local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network - if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { - return ErrUnderpriced + if pool.chainconfig.IsQuorum { + // Quorum + // Gas price must be zero for Quorum transaction + if tx.GasPriceIntCmp(common.Big0) != 0 { + return ErrInvalidGasPrice + } + // Ether value is not currently supported on private transactions + if tx.IsPrivate() && (len(tx.Data()) == 0 || tx.Value().Sign() != 0) { + return ErrEtherValueUnsupported + } + // Quorum - check if the sender account is authorized to perform the transaction + if err := pcore.CheckAccountPermission(tx.From(), tx.To(), tx.Value(), tx.Data(), tx.Gas(), tx.GasPrice()); err != nil { + return err + } + } else { + // Drop non-local transactions under our own minimal accepted gas price + local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network + if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { + return ErrUnderpriced + } } // Ensure the transaction adheres to nonce ordering if pool.currentState.GetNonce(from) > tx.Nonce() { @@ -581,7 +620,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // If the transaction pool is full, discard underpriced transactions if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it - if !local && pool.priced.Underpriced(tx, pool.locals) { + if !pool.chainconfig.IsQuorum && !local && pool.priced.Underpriced(tx, pool.locals) { log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) underpricedTxMeter.Mark(1) return false, ErrUnderpriced @@ -1150,7 +1189,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { if newHead == nil { newHead = pool.chain.CurrentBlock().Header() // Special case during testing } - statedb, err := pool.chain.StateAt(newHead.Root) + statedb, _, err := pool.chain.StateAt(newHead.Root) if err != nil { log.Error("Failed to reset txpool state", "err", err) return @@ -1173,6 +1212,11 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Transaction { + isQuorum := pool.chainconfig.IsQuorum + // Init delayed since tx pool could have been started before any state sync + if isQuorum && pool.pendingNonces == nil { + pool.reset(nil, nil) + } // Track the promoted transactions to broadcast them at once var promoted []*types.Transaction @@ -1188,20 +1232,24 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans hash := tx.Hash() pool.all.Remove(hash) } - log.Trace("Removed old queued transactions", "count", len(forwards)) - // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) - for _, tx := range drops { - hash := tx.Hash() - pool.all.Remove(hash) + var drops types.Transactions + if !isQuorum { + log.Trace("Removed old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) + drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) + for _, tx := range drops { + hash := tx.Hash() + pool.all.Remove(hash) + } + log.Trace("Removed unpayable queued transactions", "count", len(drops)) + queuedNofundsMeter.Mark(int64(len(drops))) } - log.Trace("Removed unpayable queued transactions", "count", len(drops)) - queuedNofundsMeter.Mark(int64(len(drops))) // Gather all executable transactions and promote them readies := list.Ready(pool.pendingNonces.get(addr)) for _, tx := range readies { hash := tx.Hash() + log.Trace("Promoting queued transaction", "hash", hash) if pool.promoteTx(addr, hash, tx) { promoted = append(promoted, tx) } @@ -1584,6 +1632,11 @@ func (t *txLookup) Remove(hash common.Hash) { delete(t.all, hash) } +// helper function to return chainHeadChannel size +func GetChainHeadChannleSize() int { + return chainHeadChanSize +} + // numSlots calculates the number of slots needed for a single transaction. func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index c436a309f3..0ab6eccc96 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -24,6 +24,7 @@ import ( "math/big" "math/rand" "os" + "reflect" "testing" "time" @@ -46,9 +47,10 @@ func init() { } type testBlockChain struct { - statedb *state.StateDB - gasLimit uint64 - chainHeadFeed *event.Feed + statedb *state.StateDB + privateStateDb *state.StateDB + gasLimit uint64 + chainHeadFeed *event.Feed } func (bc *testBlockChain) CurrentBlock() *types.Block { @@ -61,8 +63,8 @@ func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block return bc.CurrentBlock() } -func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { - return bc.statedb, nil +func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, *state.StateDB, error) { + return bc.statedb, bc.privateStateDb, nil } func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription { @@ -86,16 +88,25 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key return tx } -func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { +// Quorum - created setupTxPoolWithConfig(...) from original setupTxPool() to allow passing a ChainConfig as argument +func setupTxPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 10000000, new(event.Feed)} key, _ := crypto.GenerateKey() - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := NewTxPool(testTxPoolConfig, config, blockchain) return pool, key } +func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { + return setupTxPoolWithConfig(params.TestChainConfig) +} + +func setupQuorumTxPool() (*TxPool, *ecdsa.PrivateKey) { + return setupTxPoolWithConfig(params.QuorumTestChainConfig) +} + // validateTxPoolInternals checks various consistency invariants within the pool. func validateTxPoolInternals(pool *TxPool) error { pool.mu.RLock() @@ -166,7 +177,7 @@ type testChain struct { // testChain.State() is used multiple times to reset the pending state. // when simulate is true it will create a state that indicates // that tx0 and tx1 are included in the chain. -func (c *testChain) State() (*state.StateDB, error) { +func (c *testChain) State() (*state.StateDB, *state.StateDB, error) { // delay "state change" by one. The tx pool fetches the // state multiple times and by delaying it a bit we simulate // a state change between those fetches. @@ -178,7 +189,7 @@ func (c *testChain) State() (*state.StateDB, error) { c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) *c.trigger = false } - return stdb, nil + return stdb, stdb, nil } // This test simulates a scenario where a new block is imported during a @@ -196,7 +207,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { // setup pool with 2 transaction in it statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) - blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed)}, address, &trigger} + blockchain := &testChain{&testBlockChain{statedb, statedb, 1000000000, new(event.Feed)}, address, &trigger} tx0 := transaction(0, 100000, key) tx1 := transaction(1, 100000, key) @@ -230,6 +241,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { } } +// Test for transactions that are invalid on Ethereum & Quorum func TestInvalidTransactions(t *testing.T) { t.Parallel() @@ -241,29 +253,131 @@ func TestInvalidTransactions(t *testing.T) { pool.currentState.AddBalance(from, big.NewInt(1)) if err := pool.AddRemote(tx); err != ErrInsufficientFunds { - t.Error("expected", ErrInsufficientFunds) + t.Error("expected", ErrInsufficientFunds, "; got", err) } balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice())) pool.currentState.AddBalance(from, balance) if err := pool.AddRemote(tx); !errors.Is(err, ErrIntrinsicGas) { - t.Error("expected", ErrIntrinsicGas, "got", err) + t.Error("expected", ErrIntrinsicGas, "; got", err) } pool.currentState.SetNonce(from, 1) pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff)) tx = transaction(0, 100000, key) if err := pool.AddRemote(tx); err != ErrNonceTooLow { - t.Error("expected", ErrNonceTooLow) + t.Error("expected", ErrNonceTooLow, "; got", err) } tx = transaction(1, 100000, key) pool.gasPrice = big.NewInt(1000) if err := pool.AddRemote(tx); err != ErrUnderpriced { - t.Error("expected", ErrUnderpriced, "got", err) + t.Error("expected", ErrUnderpriced, "; got", err) } if err := pool.AddLocal(tx); err != nil { - t.Error("expected", nil, "got", err) + t.Error("expected", nil, "; got", err) + } + + tooMuchGas := pool.currentMaxGas + 1 + tx1 := transaction(2, tooMuchGas, key) + if err := pool.AddRemote(tx1); err != ErrGasLimit { + t.Error("expected", ErrGasLimit, "; got", err) + } + + data := make([]byte, (64*1024)+1) + tx2, _ := types.SignTx(types.NewTransaction(2, common.Address{}, big.NewInt(100), 100000, big.NewInt(1), data), types.HomesteadSigner{}, key) + if err := pool.AddRemote(tx2); err != ErrOversizedData { + t.Error("expected", ErrOversizedData, "; got", err) + } + + // Quorum + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} + params.QuorumTestChainConfig.TransactionSizeLimit = 128 + pool2 := NewTxPool(testTxPoolConfig, params.QuorumTestChainConfig, blockchain) + + pool2.currentState.AddBalance(from, big.NewInt(0xffffffffffffff)) + data2 := make([]byte, (127 * 1024)) + + tx3, _ := types.SignTx(types.NewTransaction(2, common.Address{}, big.NewInt(100), 100000, big.NewInt(0), data2), types.HomesteadSigner{}, key) + if err := pool2.AddRemote(tx3); err != ErrIntrinsicGas { + t.Error("expected", ErrIntrinsicGas, "; got", err) + } + + data3 := make([]byte, (128*1024)+1) + tx4, _ := types.SignTx(types.NewTransaction(2, common.Address{}, big.NewInt(100), 100000, big.NewInt(0), data3), types.HomesteadSigner{}, key) + if err := pool2.AddRemote(tx4); err != ErrOversizedData { + t.Error("expected", ErrOversizedData, "; got", err) + } + + tx5, _ := types.SignTx(types.NewTransaction(1, common.Address{}, big.NewInt(100), 0, big.NewInt(0), nil), types.HomesteadSigner{}, key) + balance = new(big.Int).Add(tx5.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx5.Gas()), tx5.GasPrice())) + + from, _ = deriveSender(tx5) + pool2.currentState.AddBalance(from, balance) + tx5.SetPrivate() + if err := pool2.AddRemote(tx5); err != ErrEtherValueUnsupported { + t.Error("expected", ErrEtherValueUnsupported, "; got", err) + } +} + +//Test for transactions that are only invalid on Quorum +func TestQuorumInvalidTransactions(t *testing.T) { + pool, key := setupQuorumTxPool() + defer pool.Stop() + + tx := transaction(0, 0, key) + if err := pool.AddRemote(tx); err != ErrInvalidGasPrice { + t.Error("expected", ErrInvalidGasPrice, "; got", err) + } + +} + +func TestValidateTx_whenValueZeroTransferForPrivateTransaction(t *testing.T) { + pool, key := setupQuorumTxPool() + defer pool.Stop() + zeroValue := common.Big0 + zeroGasPrice := common.Big0 + defaultTxPoolGasLimit := uint64(1000000) + arbitraryTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, zeroValue, defaultTxPoolGasLimit, zeroGasPrice, nil), types.HomesteadSigner{}, key) + arbitraryTx.SetPrivate() + + if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported { + t.Error("expected:", ErrEtherValueUnsupported, "; got:", err) + } +} + +func TestValidateTx_whenValueNonZeroTransferForPrivateTransaction(t *testing.T) { + pool, key := setupQuorumTxPool() + defer pool.Stop() + arbitraryValue := common.Big3 + arbitraryTx, balance, from := newPrivateTransaction(arbitraryValue, nil, key) + pool.currentState.AddBalance(from, balance) + + if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported { + t.Error("expected: ", ErrEtherValueUnsupported, "; got:", err) + } +} + +func newPrivateTransaction(value *big.Int, data []byte, key *ecdsa.PrivateKey) (*types.Transaction, *big.Int, common.Address) { + zeroGasPrice := common.Big0 + defaultTxPoolGasLimit := uint64(1000000) + arbitraryTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, value, defaultTxPoolGasLimit, zeroGasPrice, data), types.HomesteadSigner{}, key) + arbitraryTx.SetPrivate() + balance := new(big.Int).Add(arbitraryTx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(arbitraryTx.Gas()), arbitraryTx.GasPrice())) + from, _ := deriveSender(arbitraryTx) + return arbitraryTx, balance, from +} + +func TestValidateTx_whenValueNonZeroWithSmartContractForPrivateTransaction(t *testing.T) { + pool, key := setupQuorumTxPool() + defer pool.Stop() + arbitraryValue := common.Big3 + arbitraryTx, balance, from := newPrivateTransaction(arbitraryValue, []byte("arbitrary bytecode"), key) + pool.currentState.AddBalance(from, balance) + + if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported { + t.Error("expected: ", ErrEtherValueUnsupported, "; got:", err) } } @@ -349,7 +463,7 @@ func TestTransactionChainFork(t *testing.T) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) - pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} + pool.chain = &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} <-pool.requestReset(nil, nil) } resetState() @@ -378,7 +492,7 @@ func TestTransactionDoubleNonce(t *testing.T) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) - pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} + pool.chain = &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} <-pool.requestReset(nil, nil) } resetState() @@ -567,7 +681,7 @@ func TestTransactionPostponing(t *testing.T) { // Create the pool to test the postponing with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -780,7 +894,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { // Create the pool to test the limit enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.NoLocals = nolocals @@ -872,7 +986,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { // Create the pool to test the non-expiration enforcement statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.Lifetime = time.Second @@ -975,7 +1089,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.GlobalSlots = config.AccountSlots * 10 @@ -1022,7 +1136,7 @@ func TestTransactionAllowedTxSize(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPool() + pool, key := setupQuorumTxPool() defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1039,23 +1153,25 @@ func TestTransactionAllowedTxSize(t *testing.T) { // - signature == 65 bytes // All those fields are summed up to at most 213 bytes. baseSize := uint64(213) + txMaxSize := params.QuorumTestChainConfig.TransactionSizeLimit * 1024 dataSize := txMaxSize - baseSize // Try adding a transaction with maximal allowed size - tx := pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(1), key, dataSize) + gasPrice := big.NewInt(0) + tx := pricedDataTransaction(0, pool.currentMaxGas, gasPrice, key, dataSize) if err := pool.addRemoteSync(tx); err != nil { t.Fatalf("failed to add transaction of size %d, close to maximal: %v", int(tx.Size()), err) } // Try adding a transaction with random allowed size - if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentMaxGas, big.NewInt(1), key, uint64(rand.Intn(int(dataSize))))); err != nil { + if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentMaxGas, gasPrice, key, uint64(rand.Intn(int(dataSize))))); err != nil { t.Fatalf("failed to add transaction of random allowed size: %v", err) } // Try adding a transaction of minimal not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, txMaxSize)); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, gasPrice, key, txMaxSize)); err == nil { t.Fatalf("expected rejection on slightly oversize transaction") } // Try adding a transaction of random not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, gasPrice, key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { t.Fatalf("expected rejection on oversize transaction") } // Run some sanity checks on the pool internals @@ -1077,7 +1193,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.AccountSlots = 2 @@ -1111,7 +1227,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.GlobalSlots = 1 @@ -1159,7 +1275,7 @@ func TestTransactionPoolRepricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1280,7 +1396,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1342,7 +1458,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.GlobalSlots = 2 @@ -1448,7 +1564,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.GlobalSlots = 128 @@ -1513,7 +1629,7 @@ func TestTransactionDeduplication(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1579,7 +1695,7 @@ func TestTransactionReplacement(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1674,7 +1790,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { // Create the original pool to inject transaction into the journal statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} config := testTxPoolConfig config.NoLocals = nolocals @@ -1716,7 +1832,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive pool.Stop() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain = &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool = NewTxPool(config, params.TestChainConfig, blockchain) @@ -1743,7 +1859,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { pool.Stop() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain = &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool = NewTxPool(config, params.TestChainConfig, blockchain) pending, queued = pool.Stats() @@ -1772,7 +1888,7 @@ func TestTransactionStatusCheck(t *testing.T) { // Create the pool to test the status retrievals with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1923,3 +2039,47 @@ func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { } } } + +//Checks that the EIP155 signer is assigned to the TxPool no matter the configuration, even invalid config +func TestEIP155SignerOnTxPool(t *testing.T) { + var flagtests = []struct { + name string + homesteadBlock *big.Int + eip155Block *big.Int + }{ + {"hsnileip155nil", nil, nil}, + {"hsnileip1550", nil, big.NewInt(0)}, + {"hsnileip155100", nil, big.NewInt(100)}, + {"hs0eip155nil", big.NewInt(0), nil}, + {"hs0eip1550", big.NewInt(0), big.NewInt(0)}, + {"hs0eip155100", big.NewInt(0), big.NewInt(100)}, + {"hs100eip155nil", big.NewInt(100), nil}, + {"hs100eip1550", big.NewInt(100), big.NewInt(0)}, + {"hs100eip155100", big.NewInt(100), big.NewInt(100)}, + } + + for _, tt := range flagtests { + t.Run("", func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + statedb, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} + + chainconfig := ¶ms.ChainConfig{ + ChainID: big.NewInt(10), + HomesteadBlock: tt.homesteadBlock, + EIP150Block: big.NewInt(0), + EIP155Block: tt.eip155Block, + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + } + + pool := NewTxPool(testTxPoolConfig, chainconfig, blockchain) + + if reflect.TypeOf(types.EIP155Signer{}) != reflect.TypeOf(pool.signer) { + t.Fail() + } + }) + } + +} diff --git a/core/types.go b/core/types.go index 4c5b74a498..b57427d916 100644 --- a/core/types.go +++ b/core/types.go @@ -39,7 +39,7 @@ type Prefetcher interface { // Prefetch processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb, but any changes are discarded. The // only goal is to pre-cache transaction signatures and state trie nodes. - Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *uint32) + Prefetch(block *types.Block, statedb, privatestdb *state.StateDB, cfg vm.Config, interrupt *uint32) } // Processor is an interface for processing blocks using a given initial state. @@ -47,5 +47,5 @@ type Processor interface { // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. - Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) + Process(block *types.Block, statedb, privateState *state.StateDB, cfg vm.Config) (types.Receipts, types.Receipts, []*types.Log, uint64, error) } diff --git a/core/types/block.go b/core/types/block.go index f6f5f14903..a92a253635 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -101,6 +101,14 @@ type headerMarshaling struct { // Hash returns the block hash of the header, which is simply the keccak256 hash of its // RLP encoding. func (h *Header) Hash() common.Hash { + // If the mix digest is equivalent to the predefined Istanbul digest, use Istanbul + // specific hash calculation. + if h.MixDigest == IstanbulDigest { + // Seal is reserved in extra-data. To prove block is signed by the proposer. + if istanbulHeader := IstanbulFilteredHeader(h, true); istanbulHeader != nil { + return rlpHash(istanbulHeader) + } + } return rlpHash(h) } @@ -174,6 +182,10 @@ type Block struct { ReceivedFrom interface{} } +func (b *Block) String() string { + return fmt.Sprintf("{Header: %v}", b.header) +} + // DeprecatedTd is an old relic for extracting the TD of a block. It is in the // code solely to facilitate upgrading the database from the old format to the // new, after which it should be deleted. Do not use! diff --git a/core/types/bloom9.go b/core/types/bloom9.go index d045c9e667..4970477d4d 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -63,6 +63,15 @@ func (b *Bloom) Add(d *big.Int) { b.SetBytes(bin.Bytes()) } +// Quorum +// OrBloom executes an Or operation on the bloom +func (b *Bloom) OrBloom(bl []byte) { + bin := new(big.Int).SetBytes(b[:]) + input := new(big.Int).SetBytes(bl[:]) + bin.Or(bin, input) + b.SetBytes(bin.Bytes()) +} + // Big converts b to a big integer. func (b Bloom) Big() *big.Int { return new(big.Int).SetBytes(b[:]) diff --git a/core/types/istanbul.go b/core/types/istanbul.go new file mode 100644 index 0000000000..3375dbbda3 --- /dev/null +++ b/core/types/istanbul.go @@ -0,0 +1,107 @@ +// Copyright 2017 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 . + +package types + +import ( + "errors" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + // IstanbulDigest represents a hash of "Istanbul practical byzantine fault tolerance" + // to identify whether the block is from Istanbul consensus engine + IstanbulDigest = common.HexToHash("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365") + + IstanbulExtraVanity = 32 // Fixed number of extra-data bytes reserved for validator vanity + IstanbulExtraSeal = 65 // Fixed number of extra-data bytes reserved for validator seal + + // ErrInvalidIstanbulHeaderExtra is returned if the length of extra-data is less than 32 bytes + ErrInvalidIstanbulHeaderExtra = errors.New("invalid istanbul header extra-data") +) + +type IstanbulExtra struct { + Validators []common.Address + Seal []byte + CommittedSeal [][]byte +} + +// EncodeRLP serializes ist into the Ethereum RLP format. +func (ist *IstanbulExtra) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{ + ist.Validators, + ist.Seal, + ist.CommittedSeal, + }) +} + +// DecodeRLP implements rlp.Decoder, and load the istanbul fields from a RLP stream. +func (ist *IstanbulExtra) DecodeRLP(s *rlp.Stream) error { + var istanbulExtra struct { + Validators []common.Address + Seal []byte + CommittedSeal [][]byte + } + if err := s.Decode(&istanbulExtra); err != nil { + return err + } + ist.Validators, ist.Seal, ist.CommittedSeal = istanbulExtra.Validators, istanbulExtra.Seal, istanbulExtra.CommittedSeal + return nil +} + +// ExtractIstanbulExtra extracts all values of the IstanbulExtra from the header. It returns an +// error if the length of the given extra-data is less than 32 bytes or the extra-data can not +// be decoded. +func ExtractIstanbulExtra(h *Header) (*IstanbulExtra, error) { + if len(h.Extra) < IstanbulExtraVanity { + return nil, ErrInvalidIstanbulHeaderExtra + } + + var istanbulExtra *IstanbulExtra + err := rlp.DecodeBytes(h.Extra[IstanbulExtraVanity:], &istanbulExtra) + if err != nil { + return nil, err + } + return istanbulExtra, nil +} + +// IstanbulFilteredHeader returns a filtered header which some information (like seal, committed seals) +// are clean to fulfill the Istanbul hash rules. It returns nil if the extra-data cannot be +// decoded/encoded by rlp. +func IstanbulFilteredHeader(h *Header, keepSeal bool) *Header { + newHeader := CopyHeader(h) + istanbulExtra, err := ExtractIstanbulExtra(newHeader) + if err != nil { + return nil + } + + if !keepSeal { + istanbulExtra.Seal = []byte{} + } + istanbulExtra.CommittedSeal = [][]byte{} + + payload, err := rlp.EncodeToBytes(&istanbulExtra) + if err != nil { + return nil + } + + newHeader.Extra = append(newHeader.Extra[:IstanbulExtraVanity], payload...) + + return newHeader +} diff --git a/core/types/istanbul_test.go b/core/types/istanbul_test.go new file mode 100644 index 0000000000..db5453b53a --- /dev/null +++ b/core/types/istanbul_test.go @@ -0,0 +1,88 @@ +// Copyright 2017 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 . + +package types + +import ( + "bytes" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func TestHeaderHash(t *testing.T) { + // 0xcefefd3ade63a5955bca4562ed840b67f39e74df217f7e5f7241a6e9552cca70 + expectedExtra := common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000000f89af8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b440b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0") + expectedHash := common.HexToHash("0xcefefd3ade63a5955bca4562ed840b67f39e74df217f7e5f7241a6e9552cca70") + + // for istanbul consensus + header := &Header{MixDigest: IstanbulDigest, Extra: expectedExtra} + if !reflect.DeepEqual(header.Hash(), expectedHash) { + t.Errorf("expected: %v, but got: %v", expectedHash.Hex(), header.Hash().Hex()) + } + + // append useless information to extra-data + unexpectedExtra := append(expectedExtra, []byte{1, 2, 3}...) + header.Extra = unexpectedExtra + if !reflect.DeepEqual(header.Hash(), rlpHash(header)) { + t.Errorf("expected: %v, but got: %v", rlpHash(header).Hex(), header.Hash().Hex()) + } +} + +func TestExtractToIstanbul(t *testing.T) { + testCases := []struct { + vanity []byte + istRawData []byte + expectedResult *IstanbulExtra + expectedErr error + }{ + { + // normal case + bytes.Repeat([]byte{0x00}, IstanbulExtraVanity), + hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0"), + &IstanbulExtra{ + Validators: []common.Address{ + common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")), + common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")), + common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")), + common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")), + }, + Seal: []byte{}, + CommittedSeal: [][]byte{}, + }, + nil, + }, + { + // insufficient vanity + bytes.Repeat([]byte{0x00}, IstanbulExtraVanity-1), + nil, + nil, + ErrInvalidIstanbulHeaderExtra, + }, + } + for _, test := range testCases { + h := &Header{Extra: append(test.vanity, test.istRawData...)} + istanbulExtra, err := ExtractIstanbulExtra(h) + if err != test.expectedErr { + t.Errorf("expected: %v, but got: %v", test.expectedErr, err) + } + if !reflect.DeepEqual(istanbulExtra, test.expectedResult) { + t.Errorf("expected: %v, but got: %v", test.expectedResult, istanbulExtra) + } + } +} diff --git a/core/types/transaction.go b/core/types/transaction.go index da691bb03f..6afee574d8 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -19,6 +19,7 @@ package types import ( "container/heap" "errors" + "fmt" "io" "math/big" "sync/atomic" @@ -26,6 +27,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private/engine" "github.com/ethereum/go-ethereum/rlp" ) @@ -35,12 +38,33 @@ var ( ErrInvalidSig = errors.New("invalid transaction v, r, s values") ) +// Quorum +// deriveSigner makes a *best* guess about which signer to use. +func deriveSigner(V *big.Int) Signer { + // joel: this is one of the two places we used a wrong signer to print txes + if V.Sign() != 0 && isProtectedV(V) { + return NewEIP155Signer(deriveChainId(V)) + } else if isPrivate(V) { + return QuorumPrivateTxSigner{} + } else { + return HomesteadSigner{} + } +} + +// End Quorum + type Transaction struct { data txdata // caches hash atomic.Value size atomic.Value from atomic.Value + + privacyMetadata *PrivacyMetadata +} + +type PrivacyMetadata struct { + PrivacyFlag engine.PrivacyFlagType } type txdata struct { @@ -104,6 +128,19 @@ func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit return &Transaction{data: d} } +// Quorum +func NewTxPrivacyMetadata(privacyFlag engine.PrivacyFlagType) *PrivacyMetadata { + return &PrivacyMetadata{ + PrivacyFlag: privacyFlag, + } +} + +func (tx *Transaction) SetTxPrivacyMetadata(pm *PrivacyMetadata) { + tx.privacyMetadata = pm +} + +// End Quorum + // ChainId returns which chain id this transaction was signed for (if at all) func (tx *Transaction) ChainId() *big.Int { return deriveChainId(tx.data.V) @@ -117,7 +154,8 @@ func (tx *Transaction) Protected() bool { func isProtectedV(V *big.Int) bool { if V.BitLen() <= 8 { v := V.Uint64() - return v != 27 && v != 28 + // 27 / 28 are pre eip 155 -- ie unprotected. + return !(v == 27 || v == 28) } // anything not 27 or 28 is considered protected return true @@ -181,9 +219,10 @@ func (tx *Transaction) GasPriceCmp(other *Transaction) int { func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { return tx.data.Price.Cmp(other) } -func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } -func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } -func (tx *Transaction) CheckNonce() bool { return true } +func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } +func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } +func (tx *Transaction) CheckNonce() bool { return true } +func (tx *Transaction) PrivacyMetadata() *PrivacyMetadata { return tx.privacyMetadata } // To returns the recipient address of the transaction. // It returns nil if the transaction is a contract creation. @@ -195,6 +234,14 @@ func (tx *Transaction) To() *common.Address { return &to } +func (tx *Transaction) From() common.Address { + signer := deriveSigner(tx.data.V) + if from, err := Sender(signer, tx); err == nil { + return from + } + return common.Address{} +} + // Hash hashes the RLP encoding of tx. // It uniquely identifies the transaction. func (tx *Transaction) Hash() common.Hash { @@ -232,6 +279,7 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) { amount: tx.data.Amount, data: tx.data.Payload, checkNonce: true, + isPrivate: tx.IsPrivate(), } var err error @@ -264,6 +312,58 @@ func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { return tx.data.V, tx.data.R, tx.data.S } +func (tx *Transaction) String() string { + var from, to string + if tx.data.V != nil { + // make a best guess about the signer and use that to derive + // the sender. + signer := deriveSigner(tx.data.V) + if f, err := Sender(signer, tx); err != nil { // derive but don't cache + from = "[invalid sender: invalid sig]" + } else { + from = fmt.Sprintf("%x", f[:]) + } + } else { + from = "[invalid sender: nil V field]" + } + + if tx.data.Recipient == nil { + to = "[contract creation]" + } else { + to = fmt.Sprintf("%x", tx.data.Recipient[:]) + } + enc, _ := rlp.EncodeToBytes(&tx.data) + return fmt.Sprintf(` + TX(%x) + Contract: %v + From: %s + To: %s + Nonce: %v + GasPrice: %#x + GasLimit %#x + Value: %#x + Data: 0x%x + V: %#x + R: %#x + S: %#x + Hex: %x +`, + tx.Hash(), + tx.data.Recipient == nil, + from, + to, + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Amount, + tx.data.Payload, + tx.data.V, + tx.data.R, + tx.data.S, + enc, + ) +} + // Transactions is a Transaction slice type for basic sorting. type Transactions []*Transaction @@ -344,10 +444,14 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa // Initialize a price based heap with the head transactions heads := make(TxByPrice, 0, len(txs)) for from, accTxs := range txs { - heads = append(heads, accTxs[0]) // Ensure the sender address is from the signer - acc, _ := Sender(signer, accTxs[0]) - txs[acc] = accTxs[1:] + acc, err := Sender(signer, accTxs[0]) + if err == nil { + heads = append(heads, accTxs[0]) + txs[acc] = accTxs[1:] + } else { + log.Info("Failed to recovered sender address, this transaction is skipped", "from", from, "nonce", accTxs[0].data.AccountNonce, "err", err) + } if from != acc { delete(txs, from) } @@ -400,6 +504,7 @@ type Message struct { gasPrice *big.Int data []byte checkNonce bool + isPrivate bool } func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message { @@ -423,3 +528,36 @@ func (m Message) Gas() uint64 { return m.gasLimit } func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Data() []byte { return m.data } func (m Message) CheckNonce() bool { return m.checkNonce } + +// Quorum +func (m Message) IsPrivate() bool { + return m.isPrivate +} + +func (tx *Transaction) IsPrivate() bool { + if tx.data.V == nil { + return false + } + return tx.data.V.Uint64() == 37 || tx.data.V.Uint64() == 38 +} + +/* + * Indicates that a transaction is private, but doesn't necessarily set the correct v value, as it can be called on + * an unsigned transaction. + * pre homestead signer, all v values were v=27 or v=28, with EIP155Signer that change, + * but SetPrivate() is also used on unsigned transactions to temporarily set the v value to indicate + * the transaction is intended to be private, and so that the correct signer can be selected. The signer will correctly + * set the valid v value (37 or 38): This helps minimize changes vs upstream go-ethereum code. + */ +func (tx *Transaction) SetPrivate() { + if tx.IsPrivate() { + return + } + if tx.data.V.Int64() == 28 { + tx.data.V.SetUint64(38) + } else { + tx.data.V.SetUint64(37) + } +} + +// End Quorum diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 842fedbd03..41ae8f53a0 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -125,6 +125,9 @@ func (s EIP155Signer) Equal(s2 Signer) bool { var big8 = big.NewInt(8) func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { + if tx.IsPrivate() { + return QuorumPrivateTxSigner{}.Sender(tx) + } if !tx.Protected() { return HomesteadSigner{}.Sender(tx) } @@ -223,7 +226,14 @@ func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (commo if Vb.BitLen() > 8 { return common.Address{}, ErrInvalidSig } - V := byte(Vb.Uint64() - 27) + var offset uint64 + // private transaction has a v value of 37 or 38 + if isPrivate(Vb) { + offset = 37 + } else { + offset = 27 + } + V := byte(Vb.Uint64() - offset) if !crypto.ValidateSignatureValues(V, R, S, homestead) { return common.Address{}, ErrInvalidSig } @@ -253,6 +263,7 @@ func deriveChainId(v *big.Int) *big.Int { if v == 27 || v == 28 { return new(big.Int) } + // TODO(joel): this given v = 37 / 38 this constrains us to chain id 1 return new(big.Int).SetUint64((v - 35) / 2) } v = new(big.Int).Sub(v, big.NewInt(35)) diff --git a/core/types/transaction_signing_private.go b/core/types/transaction_signing_private.go new file mode 100644 index 0000000000..a3130664d9 --- /dev/null +++ b/core/types/transaction_signing_private.go @@ -0,0 +1,61 @@ +// Copyright 2016 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 . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Signs with Homestead +// obtains sender from EIP55Signer +type QuorumPrivateTxSigner struct{ HomesteadSigner } + +func (s QuorumPrivateTxSigner) Sender(tx *Transaction) (common.Address, error) { + return HomesteadSigner{}.Sender(tx) +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (qs QuorumPrivateTxSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + r, s, _, err := HomesteadSigner{}.SignatureValues(tx, sig) + // update v for private transaction marker: needs to be 37 (0+37) or 38 (1+37) for a private transaction. + v := new(big.Int).SetBytes([]byte{sig[64] + 37}) + return r, s, v, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s QuorumPrivateTxSigner) Hash(tx *Transaction) common.Hash { + return s.HomesteadSigner.Hash(tx) +} + +func (s QuorumPrivateTxSigner) Equal(s2 Signer) bool { + _, ok := s2.(QuorumPrivateTxSigner) + return ok +} + +/* + * If v is `37` or `38` that marks the transaction as private in Quorum. + * Note: this means quorum chains cannot have a public ethereum chainId == 1, as the EIP155 v + * param is `37` and `38` for the public Ethereum chain. Having a private chain with a chainId ==1 + * is discouraged in the general Ethereum ecosystem. + */ +func isPrivate(v *big.Int) bool { + return v.Cmp(big.NewInt(37)) == 0 || v.Cmp(big.NewInt(38)) == 0 +} diff --git a/core/types/transaction_signing_quorum_private_test.go b/core/types/transaction_signing_quorum_private_test.go new file mode 100644 index 0000000000..694a3fd29d --- /dev/null +++ b/core/types/transaction_signing_quorum_private_test.go @@ -0,0 +1,62 @@ +// 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 . + +package types + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + testifyassert "github.com/stretchr/testify/assert" +) + +func signTxWithSigner(signer Signer, key *ecdsa.PrivateKey) (*Transaction, common.Address, error) { + addr := crypto.PubkeyToAddress(key.PublicKey) + tx := NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil) + signedTx, err := SignTx(tx, signer, key) + return signedTx, addr, err +} + +// run all the tests in this file +// $> go test $(go list ./...) -run QuorumSignPrivate + +// test with QuorumPrivateSigner +/* +* $> go test -run TestQuorumSignPrivateQuorum + */ +func TestQuorumSignPrivateQuorum(t *testing.T) { + + assert := testifyassert.New(t) + keys := []*big.Int{k0v, k1v} + + for i := 0; i < len(keys); i++ { + key, _ := createKey(crypto.S256(), keys[i]) + qpPrivateSigner := QuorumPrivateTxSigner{HomesteadSigner{}} + + signedTx, addr, err := signTxWithSigner(qpPrivateSigner, key) + assert.Nil(err, err) + assert.True(signedTx.IsPrivate(), + fmt.Sprintf("The signed transaction is not private, signedTx.data.V is [%v]", signedTx.data.V)) + from, err := Sender(qpPrivateSigner, signedTx) + assert.Nil(err, err) + assert.True(from == addr, fmt.Sprintf("Expected from == address, [%x] == [%x]", from, addr)) + } + +} diff --git a/core/types/transaction_signing_quorum_test.go b/core/types/transaction_signing_quorum_test.go new file mode 100644 index 0000000000..8cce908526 --- /dev/null +++ b/core/types/transaction_signing_quorum_test.go @@ -0,0 +1,354 @@ +// 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 . + +package types + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + testifyassert "github.com/stretchr/testify/assert" +) + +// run all the tests in this file +// $> go test $(go list ./...) -run TestSignQuorum + +// private key material to test both 0 and 1 bit for the recoveryId (v). +// key with v sign == 28 (Homestead) +var k0v, _ = new(big.Int).SetString("25807260602402504536675820444142779248993100028628438487502323668296269534891", 10) + +// key with v sign == 27 (Homestead) +var k1v, _ = new(big.Int).SetString("10148397294747000913768625849546502595195728826990639993137198410557736548965", 10) + +// helper to deterministically create an ECDSA key from an int. +func createKey(c elliptic.Curve, k *big.Int) (*ecdsa.PrivateKey, error) { + sk := new(ecdsa.PrivateKey) + sk.PublicKey.Curve = c + sk.D = k + sk.PublicKey.X, sk.PublicKey.Y = c.ScalarBaseMult(k.Bytes()) + return sk, nil +} + +func signTx(key *ecdsa.PrivateKey, signer Signer) (*Transaction, common.Address, error) { + addr := crypto.PubkeyToAddress(key.PublicKey) + tx := NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil) + signedTx, err := SignTx(tx, signer, key) + //fmt.Printf("\ntx.data.V signTx after sign [%v] \n", signedTx.data.V) + return signedTx, addr, err +} + +/** + * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior + * + * Test public transactions signed by homestead Signer. Homestead sets the v param on a signed transaction to + * either 27 or 28. The v parameter is used for recovering the sender of the signed transaction. + * + * 1. Homestead: should be 27, 28 + * $> go test -run TestSignQuorumHomesteadPublic + */ +func TestSignQuorumHomesteadPublic(t *testing.T) { + + assert := testifyassert.New(t) + + k0, _ := createKey(crypto.S256(), k0v) + k1, _ := createKey(crypto.S256(), k1v) + + homeSinger := HomesteadSigner{} + + // odd parity should be 27 for Homestead + signedTx, addr, _ := signTx(k1, homeSinger) + + assert.True(signedTx.data.V.Cmp(big.NewInt(27)) == 0, fmt.Sprintf("v wasn't 27 it was [%v]", signedTx.data.V)) + + // recover address from signed TX + from, _ := Sender(homeSinger, signedTx) + //fmt.Printf("from [%v] == addr [%v]\n\n", from, from == addr) + assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr)) + + // even parity should be 28 for Homestead + signedTx, addr, _ = signTx(k0, homeSinger) + assert.True(signedTx.data.V.Cmp(big.NewInt(28)) == 0, fmt.Sprintf("v wasn't 28 it was [%v]\n", signedTx.data.V)) + + // recover address from signed TX + from, _ = Sender(homeSinger, signedTx) + //fmt.Printf("from [%v] == addr [%v]\n", from, from == addr) + assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr)) + +} + +/** + * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior + * + * Test the public transactions signed by the EIP155Signer. + * The EIP155Signer was introduced to protect against replay + * attacks https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md and stores + * the CHAINID in the signed transaction's `v` parameter as `v = chainId * 2 + 35`. + * + * The EthEIP155Signer change breaks private quorum transactions when the chainId == 1 (mainnet chainId), + * as the v parameter on a public transaction and on a private transaction will both be 37, 38. + * + * $> go test -run TestSignQuorumEIP155Public + */ +func TestSignQuorumEIP155Public(t *testing.T) { + + assert := testifyassert.New(t) + + k0, _ := createKey(crypto.S256(), k0v) + k1, _ := createKey(crypto.S256(), k1v) + + // chainId 1 even EIP155Signer should be 37 conflicts with private transaction + var chainId int64 = 2 // 7 2 10 + + v0 := chainId*2 + 35 // sig[64] + 35 .. where sig[64] == 0 + v1 := chainId*2 + 36 // sig[64] + 35 .. where sig[64] == 1 + + // Will calculate v to be `v = CHAINID * 2 + 35` + // To compute V: + // 2 * 2 + 35 == 39 + // 2 * 2 + 36 == 40 + // To retrieve Sender, pull out 27, 28 Eth Frontier / Homestead values. + // 39 - (2 * 2) - 8 == 27 + // 40 - (2 * 2) - 8 == 28 + EIPsigner := NewEIP155Signer(big.NewInt(chainId)) + + signedTx, addr, _ := signTx(k0, EIPsigner) + + //fmt.Printf("After signing V is [%v] \n", signedTx.data.V) + assert.True(signedTx.data.V.Cmp(big.NewInt(v0)) == 0, fmt.Sprintf("v wasn't [%v] it was [%v]\n", v0, signedTx.data.V)) + from, _ := Sender(EIPsigner, signedTx) + + assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr)) + + // chainId 1 even EIP155Signer should be 38 conflicts with private transaction + assert.False(signedTx.IsPrivate(), fmt.Sprintf("Public transaction is set to a private transition v == [%v]", signedTx.data.V)) + + signedTx, addr, _ = signTx(k1, EIPsigner) + + assert.True(signedTx.data.V.Cmp(big.NewInt(v1)) == 0, fmt.Sprintf("v wasn't [%v], it was [%v]\n", v1, signedTx.data.V)) + from, _ = Sender(EIPsigner, signedTx) + + assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr)) + +} + +/** + * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior + * + * When the signer is EIP155Signer, chainId == 1 cannot be used because the EIP155 computed `v` value conflicts + * with the private `v` value that quorum uses to indicate a private transaction: v == 37 and v == 38. + * + * $> go test -run TestSignQuorumEIP155FailPublicChain1 + */ +func TestSignQuorumEIP155FailPublicChain1(t *testing.T) { + + assert := testifyassert.New(t) + + k0, _ := createKey(crypto.S256(), k0v) + k1, _ := createKey(crypto.S256(), k1v) + + // chainId 1 even EIP155Signer should be 37.38 which conflicts with private transaction + var chainId int64 = 1 + + v0 := chainId*2 + 35 // sig[64] + 35 .. where sig[64] == 0 + v1 := chainId*2 + 36 // sig[64] + 35 .. where sig[64] == 1 + + // Will calculate v to be `v = CHAINID * 2 + 35` + // To compute V: + // 2 * 1 + 35 == 37 + // 2 * 1 + 36 == 38 + // To retrieve Sender, pull out 27, 28 Eth Frontier / Homestead values. + // 37 - (1 * 2) - 8 == 27 + // 38 - (1 * 2) - 8 == 28 + EIPsigner := NewEIP155Signer(big.NewInt(chainId)) + + signedTx, addr, _ := signTx(k0, EIPsigner) + + // the calculated v value should equal `chainId * 2 + 35 ` + assert.True(signedTx.data.V.Cmp(big.NewInt(v0)) == 0, fmt.Sprintf("v wasn't [%v] it was "+ + "[%v]\n", v0, signedTx.data.V)) + // the sender will not be equal as HomesteadSigner{}.Sender(tx) is used because IsPrivate() will be true + // although it is a public tx. + // This is test to catch when / if this behavior changes. + assert.True(signedTx.IsPrivate(), "A public transaction with EIP155 and chainID 1 is expected to be "+ + "considered private, as its v param conflict with a private transaction. signedTx.IsPrivate() == [%v]", signedTx.IsPrivate()) + from, _ := Sender(EIPsigner, signedTx) + + assert.False(from == addr, fmt.Sprintf("Expected the sender of a public TX from chainId 1, \n "+ + "should not be recoverable from [%x] addr [%v] ", from, addr)) + + signedTx, addr, _ = signTx(k1, EIPsigner) + + // the calculated v value should equal `chainId * 2 + 35` + assert.True(signedTx.data.V.Cmp(big.NewInt(v1)) == 0, + fmt.Sprintf("v wasn't [%v] it was [%v]", v1, signedTx.data.V)) + + // the sender will not be equal as HomesteadSigner{}.Sender(tx) is used because IsPrivate() will be true + // although it is a public tx. + // This is test to catch when / if this behavior changes. + // we are signing the data with EIPsigner and chainID 1, so this would be considered a private tx. + assert.True(signedTx.IsPrivate(), "A public transaction with EIP155 and chainID 1 is expected to "+ + "to be considered private, as its v param conflict with a private transaction. "+ + "signedTx.IsPrivate() == [%v]", signedTx.IsPrivate()) + from, _ = Sender(EIPsigner, signedTx) + + assert.False(from == addr, fmt.Sprintf("Expected the sender of a public TX from chainId 1, "+ + "should not be recoverable from [%x] addr [%v] ", from, addr)) + +} + +/** +* As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior +* +* Use Homestead to sign and EIPSigner to recover. +* +* SendTransaction creates a transaction for the given argument, signs it and submit it to the transaction pool. +* func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { +* Current implementation in `internal/ethapi/api.go` +* +* accounts/keystore/keystore.SignTx(): would hash and sign with homestead +* +* When a private tx (obtained from json params PrivateFor) is submitted `internal/ethapi/api.go`: +* +* 1. sign with HomesteadSigner, this will set the v parameter to +* 27 or 28. // there is no indication that this is a private tx yet. +* +* 2. when submitting a transaction `submitTransaction(ctx context.Context, b Backend, tx *types.Transaction, isPrivate bool)` + check isPrivate param, and call `tx.SetPrivate()`, this will update the `v` signature param (recoveryID) +* from 27 -> 37, 28 -> 38. // this is now considered a private tx. +* +* $> go test -run TestSignQuorumHomesteadEIP155SigningPrivateQuorum +*/ +func TestSignQuorumHomesteadEIP155SigningPrivateQuorum(t *testing.T) { + + assert := testifyassert.New(t) + + keys := []*big.Int{k0v, k1v} + + homeSinger := HomesteadSigner{} + recoverySigner := NewEIP155Signer(big.NewInt(18)) + + // check for both sig[64] == 0, and sig[64] == 1 + for i := 0; i < len(keys); i++ { + key, _ := createKey(crypto.S256(), keys[i]) + signedTx, addr, err := signTx(key, homeSinger) + + assert.Nil(err, err) + // set to privateTX after the initial signing, this explicitly sets the v param. + // Note: only works when the tx was signed with the homesteadSinger (v==27 | 28). + signedTx.SetPrivate() + + assert.True(signedTx.IsPrivate(), fmt.Sprintf("Expected the transaction to be private [%v]", signedTx.IsPrivate())) + // Try to recover Sender + from, err := Sender(recoverySigner, signedTx) + + assert.Nil(err, err) + assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. Got %x want %x", from, addr)) + } + +} + +/* + * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior + * Use Homestead to sign and Homestead to recover. + * + * Signing private transactions with HomesteadSigner, and recovering a private transaction with + * HomesteadSigner works, but the transaction has to be set to private `signedTx.SetPrivate()` after + * the signature and before recovering the address. + * + * $> go test -run TestSignQuorumHomesteadOnlyPrivateQuorum + */ +func TestSignQuorumHomesteadOnlyPrivateQuorum(t *testing.T) { + + assert := testifyassert.New(t) + + // check even and odd parity + keys := []*big.Int{k0v, k1v} + + homeSinger := HomesteadSigner{} + recoverySigner := HomesteadSigner{} + + for i := 0; i < len(keys); i++ { + key, _ := createKey(crypto.S256(), keys[i]) + signedTx, addr, err := signTx(key, homeSinger) + + assert.Nil(err, err) + + //fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V) + // set to privateTX after the initial signing. + signedTx.SetPrivate() + assert.True(signedTx.IsPrivate(), fmt.Sprintf("Expected the transaction to be "+ + "private [%v]", signedTx.IsPrivate())) + //fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V) + + // Try to recover Sender + from, err := Sender(recoverySigner, signedTx) + + assert.Nil(err, err) + assert.True(from == addr, fmt.Sprintf("Expected from and address to be equal. "+ + " Got %x want %x", from, addr)) + } + +} + +/* + * As of quorum v2.2.3 commit be7cc31ce208525ea1822e7d0fee88bf7f14500b 30 April 2019 behavior + * + * Use EIP155 to sign and EIP155 to recover (This is not a valid combination and does **not** work). + * + * Signing private transactions with EIP155Signer, and recovering a private transaction with + * EIP155Signer does **not** work. + * note: deriveChainId only checks for 27, 28 when using EIP155 + * note: In the case where the v param is not 27 or 28 when setting private it will always be set to 37 + * + * $> go test -run TestSignQuorumEIP155OnlyPrivateQuorum + */ +func TestSignQuorumEIP155OnlyPrivateQuorum(t *testing.T) { + + assert := testifyassert.New(t) + + // check even and odd parity + keys := []*big.Int{k0v, k1v} + + EIP155Signer := NewEIP155Signer(big.NewInt(0)) + + for i := 0; i < len(keys); i++ { + key, _ := createKey(crypto.S256(), keys[i]) + signedTx, addr, err := signTx(key, EIP155Signer) + + assert.Nil(err, err) + //fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V) + + // set to privateTX after the initial signing. + signedTx.SetPrivate() + + assert.True(signedTx.IsPrivate(), fmt.Sprintf("Expected the transaction to be private [%v]", signedTx.IsPrivate())) + //fmt.Printf("Private tx.data.V Home [%v] \n", signedTx.data.V) + + // Try to recover Sender + from, err := Sender(EIP155Signer, signedTx) + + assert.Nil(err, err) + assert.False(from == addr, fmt.Sprintf("Expected recovery to fail. from [%x] should not equal "+ + "addr [%x]", from, addr)) + + } + +} diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go index 689fc38a9b..0e02bdf796 100644 --- a/core/types/transaction_signing_test.go +++ b/core/types/transaction_signing_test.go @@ -77,22 +77,25 @@ func TestEIP155ChainId(t *testing.T) { } func TestEIP155SigningVitalik(t *testing.T) { - // Test vectors come from http://vitalik.ca/files/eip155_testvec.txt + // Test vectors come from http://vitalik.ca/files/eip155_testvec.txt is not available + // Since we cannot use chainId of 1, new raw transaction and address paris are create as below rule. + // gasPrice: 20 * 10**9, gas: 21000, data: "", to: 0x3535353535353535353535353535353535353535, value: 10**18, nonce: 9, chainid: 10, + // privateKeys used are 0x4646464646464646464646464646464646464646464646464646464646464640 to 0x4646464646464646464646464646464646464646464646464646464646464649 for i, test := range []struct { txRlp, addr string }{ - {"f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xf0f6f18bca1b28cd68e4357452947e021241e9ce"}, - {"f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112"}, - {"f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be"}, - {"f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0x82a88539669a3fd524d669e858935de5e5410cf0"}, - {"f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf9358f2538fd5ccfeb848b64a96b743fcc930554"}, - {"f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xa8f7aba377317440bc5b26198a363ad22af1f3a4"}, - {"f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35"}, - {"f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xd37922162ab7cea97c97a87551ed02c9a38b7332"}, - {"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x9bddad43f934d313c2b79ca28a432dd2b7281029"}, - {"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008037a024692193af7d2c93f2b105d8d79f91106c4e44bed054b8056e0cb13d1f48c598a01126dbf7c80423fbd2c8b062812d15ad3144caea7f599eed86a17d17da5e9d5d", "0x5fAA510EB3f838aC398a293b5714ad279f9cECF4"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008038a0262959a9f060bf3e205e28e046a3331036d7824487c21f9c9a234da7bf8d947da078c0623a89396c1e49eb24c53e38c2cb44d39e82b66d3e5c3216f55883eaa18f", "0xDe6AB723c23bba740410129F9edc952fD6fbced4"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008038a0ed95916ac3f361c77d91504f7fd9e582926b96f786669356efc5027c96ea59c1a04f8c52b39d3303df7f577b2087f7a550de117f07c3d7eac8d24b2e58fd2cb722", "0x4107c605Be9cFc6C8c625bCB3f762E963472457E"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008038a0e15270bbd1f981aa17b41dba76c6e244439eab3766ac535cfd914fbe7bd45e56a05cd6920d3ad53c16fbe659450b9ff1e52bb3983998d769db5b10b6d42320e0c1", "0x68B48A376F3158362443Ee0DF16f5C30b4aCE9B7"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008038a04007e77c19387f9d59d3d9e64f2f1c961d89e76a4478e000bbba436b03605d58a00edc33fe0f5a8598cc70f8ac7c24d9d22580f51a1fe6e52872819c35df6cc955", "0x14Fe11894410453c01485a7e337c3F63fC512d14"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008037a0a45e844857928b7309f5607180680887fd98940aadd4cc21126ecbe55c4f363fa07c67cab2c7a53b09fdea0f07bce9a7cacb5edfacf19539472e17fd286af2dcf4", "0xAa9b8181391561bCe5199c5b1762aa26832EE548"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008037a0745aa5ba54d4b1cf6d264556c75abce97bfd2d07b9864a298ebcb6dc5268667fa04d6224e1036d4820ac36dfe768c3230b0450f657a2b8b459689da069f2c1a383", "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008038a007d494d57c46ffb9a7d560497751e4f75c10083b1f703b57f155193c34837a07a006f58ebd3d8df59b8d2d8ea484a861592b5c18dc9652075a080758787d2db7ae", "0x5a17650BE84F28Ed583e93E6ed0C99b1D1FC1b34"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008038a0878b3ea61b8135c5f6c6907ea6669fc0f9eb9c90e4b648923498627c4cb8b5b6a02e8de6963a8649f16cadfebf23def96cafd89072ad19ed04c29e5f880e5ede98", "0x0EfbD0bEC0dA8dCc0Ad442A7D337E9CDc2dd6a54"}, + {"f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008037a0fa0fd46b84c9d488558da70103960f1c43400b4d92cc9ca37737bd508635acefa01b2b01c2667ce7b48cc3fcfdba75181d097134ae6e57af4b691ba6d5c118d0c1", "0x0E8E18e1A11E6196f6B82426196027d042Fd6812"}, } { - signer := NewEIP155Signer(big.NewInt(1)) + signer := NewEIP155Signer(big.NewInt(10)) var tx *Transaction err := rlp.DecodeBytes(common.Hex2Bytes(test.txRlp), &tx) @@ -121,17 +124,17 @@ func TestChainId(t *testing.T) { tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil) var err error - tx, err = SignTx(tx, NewEIP155Signer(big.NewInt(1)), key) + tx, err = SignTx(tx, NewEIP155Signer(big.NewInt(10)), key) if err != nil { t.Fatal(err) } - _, err = Sender(NewEIP155Signer(big.NewInt(2)), tx) + _, err = Sender(NewEIP155Signer(big.NewInt(11)), tx) if err != ErrInvalidChainId { t.Error("expected error:", ErrInvalidChainId) } - _, err = Sender(NewEIP155Signer(big.NewInt(1)), tx) + _, err = Sender(NewEIP155Signer(big.NewInt(10)), tx) if err != nil { t.Error("expected no error") } diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 7a1b6cd4d4..8a32b2a64c 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -49,6 +49,18 @@ var ( HomesteadSigner{}, common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), ) + + rightvrsTx2, _ = NewTransaction( + 3, + common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + big.NewInt(10), + 2000, + big.NewInt(0), + common.FromHex("5544"), + ).WithSignature( + HomesteadSigner{}, + common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), + ) ) func TestTransactionSigHash(t *testing.T) { @@ -72,6 +84,18 @@ func TestTransactionEncode(t *testing.T) { } } +// Test from the original quorum implementation +func TestTransactionEncode2(t *testing.T) { + txb, err := rlp.EncodeToBytes(rightvrsTx2) + if err != nil { + t.Fatalf("encode error: %v", err) + } + should := common.FromHex("f86103808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a8255441ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3") + if !bytes.Equal(txb, should) { + t.Errorf("encoded RLP mismatch, got %x", txb) + } +} + func decodeTx(data []byte) (*Transaction, error) { var tx Transaction t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx) diff --git a/core/vm/errors.go b/core/vm/errors.go index f6b156a02e..a58b236248 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -39,6 +39,9 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidRetsub = errors.New("invalid retsub") ErrReturnStackExceeded = errors.New("return stack limit reached") + + ErrReadOnlyValueTransfer = errors.New("VM in read-only mode. Value transfer prohibited.") + ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/evm.go b/core/vm/evm.go index f5469c500c..939d989f63 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -18,16 +18,33 @@ package vm import ( "errors" + "fmt" "math/big" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/trie" ) +// note: Quorum, States, and Value Transfer +// +// In Quorum there is a tricky issue in one specific case when there is call from private state to public state: +// * The state db is selected based on the callee (public) +// * With every call there is an associated value transfer -- in our case this is 0 +// * Thus, there is an implicit transfer of 0 value from the caller to callee on the public state +// * However in our scenario the caller is private +// * Thus, the transfer creates a ghost of the private account on the public state with no value, code, or storage +// +// The solution is to skip this transfer of 0 value under Quorum + // emptyCodeHash is used by create to ensure deployment is disallowed to already // deployed contract addresses (relevant after the account abstraction). var emptyCodeHash = crypto.Keccak256Hash(nil) @@ -60,6 +77,31 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { + // Quorum + if contract.CodeAddr != nil { + + // Using CodeAddr is favour over contract.Address() + // During DelegateCall() CodeAddr is the address of the delegated account + address := *contract.CodeAddr + // during simulation/eth_call, when contract code is empty, there's no execution hence the + // multitenancy check will not happen in captureOperationMode(). + // This additional check to ensure we capture this case + if evm.SupportsMultitenancy && evm.AuthorizeMessageCallFunc != nil && len(contract.Code) == 0 { + return nil, multitenancy.ErrNotAuthorized + } + if err := evm.captureAffectedContract(address, ModeUnknown); err != nil { + return nil, err + } + // When delegatecall, need to capture the operation mode in the context of contract.Address() + // the affected contract is required read only mode + if address != contract.Address() { + evm.pushAddress(contract.Address()) + } else { + evm.pushAddress(address) + } + defer evm.popAddress() + } + // End Quorum for _, interpreter := range evm.interpreters { if interpreter.CanRun(contract.Code) { if evm.interpreter != interpreter { @@ -97,8 +139,21 @@ type Context struct { BlockNumber *big.Int // Provides information for NUMBER Time *big.Int // Provides information for TIME Difficulty *big.Int // Provides information for DIFFICULTY + + // Quorum + // EVM should consider multitenancy + SupportsMultitenancy bool + // AuthorizeCreateFunc performs tenancy authorization check for contract creation. + // It's only injected during simulation + AuthorizeCreateFunc multitenancy.AuthorizeCreateFunc + // AuthorizeMessageCallFunc performs tenancy authorization check for message call to a contract. + // It's only injected during simulation/eth_call + AuthorizeMessageCallFunc multitenancy.AuthorizeMessageCallFunc } +type PublicState StateDB +type PrivateState StateDB + // EVM is the Ethereum Virtual Machine base object and provides // the necessary tools to run a contract on the given state with // the provided context. It should be noted that any error @@ -134,11 +189,102 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + + // Quorum additions: + publicState PublicState + privateState PrivateState + states [1027]*state.StateDB // TODO(joel) we should be able to get away with 1024 or maybe 1025 + currentStateDepth uint + + // This flag has different semantics from the `Interpreter:readOnly` flag (though they interact and could maybe + // be simplified). This is set by Quorum when it's inside a Private State -> Public State read. + quorumReadOnly bool + readOnlyDepth uint + + // these are for privacy enhancements and multitenancy + affectedContracts map[common.Address]*AffectedType // affected contract account address -> type + currentTx *types.Transaction // transaction currently being applied on this EVM + addressStack []common.Address // store contract addresses being executed + // store last error during EVM execution lifecycle. + // we use this to bubble up the error instead of "evm: execution revert" error. + // use it with care as it's meant for runtime multitenancy check during simulation. + lastError error +} + +// AffectedType defines attributes indicating how a contract is affected +// as the result of the contract code execution in an EVM +type AffectedType struct { + // reason captures how the contract is affected. + // Default to MessageCall and set to Creation if the contract under execution is newly created. + reason AffectedReason + // mode captures how the state is operated as the result of contract code execution. + // The value is cached as an expectation by performing trial of ModeRead and ModeWrite against access token for a contract before execution. + // Runtime multitenancy check uses this value to verify if an opcode execution violates the expectation. + // At the end of EVM lifecycle, this reflects the actual mode. + mode AffectedMode +} + +func (t AffectedType) String() string { + return fmt.Sprintf("reason=%d,mode=%d", t.reason, t.mode) +} + +// AffectedReason defines a type of operation that was applied to a contract. +type AffectedReason byte + +const ( + _ AffectedReason = iota + Creation AffectedReason = iota + MessageCall +) + +// AffectedMode defines a mode in which the state is operated as the result of contract code execution. +type AffectedMode byte + +const ( + // ModeUnknown indicates an auxiliary mode used during initialization of an affected contract + ModeUnknown AffectedMode = iota + // ModeRead indicates that state has not been modified as the result of contract code execution + ModeRead AffectedMode = iota + // ModeWrite indicates that state has been modified as the result of contract code execution + ModeWrite = ModeRead << 1 + // ModeUpdated indicates that the affected mode has been setup for multitenancy check. + // This is mainly used during simulation and eth_call + ModeUpdated = ModeRead << 7 +) + +func ModeOf(isWrite bool) AffectedMode { + if isWrite { + return ModeWrite + } + return ModeRead +} + +func (mode AffectedMode) IsNotAuthorized(actualMode AffectedMode) bool { + return mode.Has(ModeUpdated) && !mode.Has(actualMode) +} + +func (mode AffectedMode) Update(authorizedRead bool, authorizedWrite bool) AffectedMode { + newMode := mode | ModeUpdated + if authorizedRead { + newMode = newMode | ModeRead + } + if authorizedWrite { + newMode = newMode | ModeWrite + } + return newMode +} + +func (mode AffectedMode) Has(modes ...AffectedMode) bool { + expectedMode := ModeUnknown + for _, m := range modes { + expectedMode = expectedMode | m + } + return mode&expectedMode == expectedMode } // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { +func NewEVM(ctx Context, statedb, privateState StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { evm := &EVM{ Context: ctx, StateDB: statedb, @@ -146,6 +292,12 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon chainConfig: chainConfig, chainRules: chainConfig.Rules(ctx.BlockNumber), interpreters: make([]Interpreter, 0, 1), + + publicState: statedb, + privateState: privateState, + + affectedContracts: make(map[common.Address]*AffectedType), + addressStack: make([]common.Address, 0), } if chainConfig.IsEWASM(ctx.BlockNumber) { @@ -164,6 +316,8 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon panic("No supported ewasm interpreter yet.") } + evm.Push(privateState) + // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here // as we always want to have the built-in EVM as the failover option. evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig)) @@ -196,6 +350,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.vmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } + + evm.Push(getDualState(evm, addr)) + defer func() { evm.Pop() }() + // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -218,7 +376,20 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), addr, value) + + // Quorum + if evm.ChainConfig().IsQuorum { + // skip transfer if value /= 0 (see note: Quorum, States, and Value Transfer) + if value.Sign() != 0 { + if evm.quorumReadOnly { + return nil, gas, ErrReadOnlyValueTransfer + } + evm.Transfer(evm.StateDB, caller.Address(), addr, value) + } + // End Quorum + } else { + evm.Transfer(evm.StateDB, caller.Address(), addr, value) + } // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { @@ -272,6 +443,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if evm.vmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } + + evm.Push(getDualState(evm, addr)) + defer func() { evm.Pop() }() + // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -315,6 +490,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.vmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } + + evm.Push(getDualState(evm, addr)) + defer func() { evm.Pop() }() + // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -353,18 +532,23 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + // Quorum + // use the right state (public or private) + stateDb := getDualState(evm, addr) + // End Quorum + // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced // after all empty accounts were deleted, so this is not required. However, if we omit this, // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. // We could change this, but for now it's left for legacy reasons - var snapshot = evm.StateDB.Snapshot() + var snapshot = stateDb.Snapshot() // We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.StateDB.AddBalance(addr, big0) + stateDb.AddBalance(addr, big0) if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) @@ -376,7 +560,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, stateDb.GetCodeHash(addrCopy), stateDb.GetCode(addrCopy)) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. @@ -384,7 +568,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte gas = contract.Gas } if err != nil { - evm.StateDB.RevertToSnapshot(snapshot) + stateDb.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { gas = 0 } @@ -414,8 +598,25 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } - nonce := evm.StateDB.GetNonce(caller.Address()) - evm.StateDB.SetNonce(caller.Address(), nonce+1) + + // Quorum + // Get the right state in case of a dual state environment. If a sender + // is a transaction (depth == 0) use the public state to derive the address + // and increment the nonce of the public state. If the sender is a contract + // (depth > 0) use the private state to derive the nonce and increment the + // nonce on the private state only. + // + // If the transaction went to a public contract the private and public state + // are the same. + var creatorStateDb StateDB + if evm.depth > 0 { + creatorStateDb = evm.privateState + } else { + creatorStateDb = evm.publicState + } + + nonce := creatorStateDb.GetNonce(caller.Address()) + creatorStateDb.SetNonce(caller.Address(), nonce+1) // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) @@ -424,11 +625,35 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Create a new account on the state snapshot := evm.StateDB.Snapshot() + if evm.SupportsMultitenancy && evm.AuthorizeCreateFunc != nil { + if authorized := evm.AuthorizeCreateFunc(); !authorized { + return nil, common.Address{}, gas, multitenancy.ErrNotAuthorized + } + } evm.StateDB.CreateAccount(address) + evm.affectedContracts[address] = newAffectedType(Creation, ModeWrite|ModeRead) if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } - evm.Transfer(evm.StateDB, caller.Address(), address, value) + if nil != evm.currentTx && evm.currentTx.IsPrivate() && evm.currentTx.PrivacyMetadata() != nil { + // for calls (reading contract state) or finding the affected contracts there is no transaction + if evm.currentTx.PrivacyMetadata().PrivacyFlag.IsNotStandardPrivate() { + pm := state.NewStatePrivacyMetadata(common.BytesToEncryptedPayloadHash(evm.currentTx.Data()), evm.currentTx.PrivacyMetadata().PrivacyFlag) + evm.StateDB.SetPrivacyMetadata(address, pm) + log.Trace("Set Privacy Metadata", "key", address, "privacyMetadata", pm) + } + } + if evm.ChainConfig().IsQuorum { + // skip transfer if value /= 0 (see note: Quorum, States, and Value Transfer) + if value.Sign() != 0 { + if evm.quorumReadOnly { + return nil, common.Address{}, gas, ErrReadOnlyValueTransfer + } + evm.Transfer(evm.StateDB, caller.Address(), address, value) + } + } else { + evm.Transfer(evm.StateDB, caller.Address(), address, value) + } // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -446,8 +671,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := run(evm, contract, nil, false) - // check whether the max code size has been exceeded - maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize + maxCodeSize := evm.ChainConfig().GetMaxCodeSize(evm.BlockNumber) + // check whether the max code size has been exceeded, check maxcode size from chain config + maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > maxCodeSize // if the contract creation ran successfully and no errors were returned // calculate the gas required to store the code. If the code could not // be stored due to not enough gas set an error and let it be handled @@ -483,7 +709,25 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Create creates a new contract using code as deployment code. func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { - contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) + // Quorum + // Get the right state in case of a dual state environment. If a sender + // is a transaction (depth == 0) use the public state to derive the address + // and increment the nonce of the public state. If the sender is a contract + // (depth > 0) use the private state to derive the nonce and increment the + // nonce on the private state only. + // + // If the transaction went to a public contract the private and public state + // are the same. + var creatorStateDb StateDB + if evm.depth > 0 { + creatorStateDb = evm.privateState + } else { + creatorStateDb = evm.publicState + } + + // Ensure there's no existing contract already at the designated address + nonce := creatorStateDb.GetNonce(caller.Address()) + contractAddr = crypto.CreateAddress(caller.Address(), nonce) return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr) } @@ -499,3 +743,235 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } + +// Quorum functions for dual state +func getDualState(evm *EVM, addr common.Address) StateDB { + // priv: (a) -> (b) (private) + // pub: a -> [b] (private -> public) + // priv: (a) -> b (public) + state := evm.StateDB + + if evm.PrivateState().Exist(addr) { + state = evm.PrivateState() + evm.captureAffectedContract(addr, ModeUnknown) + } else if evm.PublicState().Exist(addr) { + state = evm.PublicState() + } + + return state +} + +func (evm *EVM) PublicState() PublicState { return evm.publicState } +func (evm *EVM) PrivateState() PrivateState { return evm.privateState } +func (evm *EVM) SetCurrentTX(tx *types.Transaction) { evm.currentTx = tx } +func (evm *EVM) SetTxPrivacyMetadata(pm *types.PrivacyMetadata) { + evm.currentTx.SetTxPrivacyMetadata(pm) +} +func (evm *EVM) Push(statedb StateDB) { + // Quorum : the read only depth to be set up only once for the entire + // op code execution. This will be set first time transition from + // private state to public state happens + // statedb will be the state of the contract being called. + // if a private contract is calling a public contract make it readonly. + if !evm.quorumReadOnly && evm.privateState != statedb { + evm.quorumReadOnly = true + evm.readOnlyDepth = evm.currentStateDepth + } + + if castedStateDb, ok := statedb.(*state.StateDB); ok { + evm.states[evm.currentStateDepth] = castedStateDb + evm.currentStateDepth++ + } + + evm.StateDB = statedb +} +func (evm *EVM) Pop() { + evm.currentStateDepth-- + if evm.quorumReadOnly && evm.currentStateDepth == evm.readOnlyDepth { + evm.quorumReadOnly = false + } + evm.StateDB = evm.states[evm.currentStateDepth-1] +} + +func (evm *EVM) Depth() int { return evm.depth } + +// We only need to revert the current state because when we call from private +// public state it's read only, there wouldn't be anything to reset. +// (A)->(B)->C->(B): A failure in (B) wouldn't need to reset C, as C was flagged +// read only. +func (evm *EVM) RevertToSnapshot(snapshot int) { + evm.StateDB.RevertToSnapshot(snapshot) +} + +// Quorum +// +// Returns addresses of contracts which are message-called +func (evm *EVM) CalledContracts() []common.Address { + addr := make([]common.Address, 0, len(evm.affectedContracts)) + for a, t := range evm.affectedContracts { + if t.reason == MessageCall { + addr = append(addr, a) + } + } + return addr[:] +} + +// Quorum +// +// Returns addresses of contracts which are newly created +func (evm *EVM) CreatedContracts() []common.Address { + addr := make([]common.Address, 0, len(evm.affectedContracts)) + for a, t := range evm.affectedContracts { + if t.reason == Creation { + addr = append(addr, a) + } + } + return addr[:] +} + +// Quorum +// +// pushAddress stores the contract address being affected during EVM execution +func (evm *EVM) pushAddress(address common.Address) { + evm.addressStack = append(evm.addressStack, address) +} + +// Quorum +// +// popAddress retrieves the affected contract address from the stack +func (evm *EVM) popAddress() { + l := len(evm.addressStack) + if l == 0 { + return + } + evm.addressStack = evm.addressStack[:l-1] +} + +// Quorum +// +// peekAddress retrieves the affected contract address from the top of the stack +func (evm *EVM) peekAddress() common.Address { + l := len(evm.addressStack) + if l == 0 { + return common.Address{} + } + return evm.addressStack[l-1] +} + +// Quorum +// +// captureOperationMode stores the type of operation being applied on the current +// affected contract whose address is on top of the stack. +// For multitenancy, it checks if the mode is allowed. Also it bubbles up the last error +// captured. This helps to avoid "evm: execution revert" generic error +func (evm *EVM) captureOperationMode(isWriteOperation bool) error { + currentAddress := evm.peekAddress() + if (currentAddress == common.Address{}) { + return evm.lastError + } + actualMode := ModeOf(isWriteOperation) + if t, ok := evm.affectedContracts[currentAddress]; ok { + // perform multitenancy check + if evm.enforceMultitenancyCheck() { + if t.mode.IsNotAuthorized(actualMode) { + log.Trace("Multitenancy check for captureOperationMode()", "address", currentAddress.Hex(), "actual", actualMode, "expect", t.mode) + evm.lastError = multitenancy.ErrNotAuthorized + } + // bubble up the last error + if evm.lastError != nil { + return evm.lastError + } + } + t.mode = t.mode | actualMode + } + return nil +} + +// Quorum +// +// captureAffectedContract stores the contract address to the affectedContract list if not yet there. +// The affected mode is also updated if required. +// Default affected reason is MessageCall. +// In simulation/eth_call for multitenancy, it sets the expectation of AffectedMode +// to be verified later when an opcode is executed. +func (evm *EVM) captureAffectedContract(address common.Address, mode AffectedMode) error { + affectedType, found := evm.affectedContracts[address] + if !found { + affectedType = newAffectedType(MessageCall, mode) + evm.affectedContracts[address] = affectedType + } + if affectedType.mode != ModeUnknown { + return nil + } + if evm.SupportsMultitenancy && evm.AuthorizeMessageCallFunc != nil { + authorizedRead, authorizedWrite, err := evm.AuthorizeMessageCallFunc(address) + if err != nil { + return err + } + // if we don't authorize either read/write, it's unauthorized access + // and we need to inform EVM + if !authorizedRead && !authorizedWrite { + evm.lastError = multitenancy.ErrNotAuthorized + log.Debug("Affected contract not authorized", "address", address.Hex(), "read", authorizedRead, "write", authorizedWrite) + return multitenancy.ErrNotAuthorized + } + oldMode := affectedType.mode + affectedType.mode = affectedType.mode.Update(authorizedRead, authorizedWrite) + log.Debug("AffectedMode changed", "address", address.Hex(), "old", oldMode, "new", affectedType.mode) + } + return nil +} + +// enforceMultitenancyCheck returns true if EVM is enforced to do multitenancy check +// during simulation/eth_call, false otherwise +func (evm *EVM) enforceMultitenancyCheck() bool { + return evm.AuthorizeCreateFunc != nil || evm.AuthorizeMessageCallFunc != nil +} + +// Quorum +// +// AffecteMode returns the type of operation (read/write) which was applied on the given +// contract address. It returns ModeUnknown if the contract is not affected during +// the lifecycle of this EVM instance +func (evm *EVM) AffectedMode(a common.Address) (AffectedMode, error) { + if t, ok := evm.affectedContracts[a]; ok { + return t.mode, nil + } + return ModeUnknown, fmt.Errorf("address not found") +} + +func newAffectedType(r AffectedReason, m AffectedMode) *AffectedType { + return &AffectedType{ + reason: r, + mode: m, + } +} + +// Quorum +// +// AffectedContracts returns all affected contracts that are the results of +// MessageCall transaction +func (evm *EVM) AffectedContracts() []common.Address { + addr := make([]common.Address, 0, len(evm.affectedContracts)) + for a, t := range evm.affectedContracts { + if t.reason == MessageCall { + addr = append(addr, a) + } + } + return addr[:] +} + +// Return MerkleRoot of all affected contracts (due to both creation and message call) +func (evm *EVM) CalculateMerkleRoot() (common.Hash, error) { + combined := new(trie.Trie) + for addr := range evm.affectedContracts { + data, err := getDualState(evm, addr).GetRLPEncodedStateObject(addr) + if err != nil { + return common.Hash{}, err + } + if err := combined.TryUpdate(addr.Bytes(), data); err != nil { + return common.Hash{}, err + } + } + return combined.Hash(), nil +} diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go new file mode 100644 index 0000000000..e280e25e89 --- /dev/null +++ b/core/vm/evm_test.go @@ -0,0 +1,23 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAffectedMode_Update_whenTypical(t *testing.T) { + testObject := ModeUnknown + authorizedReads := []bool{true, false} + authorizedWrites := []bool{true, false} + for _, authorizedRead := range authorizedReads { + for _, authorizedWrite := range authorizedWrites { + actual := testObject.Update(authorizedRead, authorizedWrite) + + assert.True(t, actual.Has(ModeUpdated)) + assert.Equal(t, authorizedRead, actual.Has(ModeRead)) + assert.Equal(t, authorizedWrite, actual.Has(ModeWrite)) + assert.False(t, testObject.Has(ModeUpdated)) + } + } +} diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 6655f9bf42..b8cc5e5a14 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -95,8 +95,9 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( + db = getDualState(evm, contract.Address()) y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) + current = db.GetState(contract.Address(), common.Hash(x.Bytes32())) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -332,10 +333,10 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize address = common.Address(stack.Back(1).Bytes20()) ) if evm.chainRules.IsEIP158 { - if transfersValue && evm.StateDB.Empty(address) { + if transfersValue && getDualState(evm, address).Empty(address) { gas += params.CallNewAccountGas } - } else if !evm.StateDB.Exist(address) { + } else if !getDualState(evm, address).Exist(address) { gas += params.CallNewAccountGas } if transfersValue { diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 419c903062..6a273c9584 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -91,7 +91,7 @@ func TestEIP2200(t *testing.T) { CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true }, Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, } - vmenv := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) + vmenv := NewEVM(vmctx, statedb, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int)) if err != tt.failure { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index adf44b7f48..00460d60cc 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -258,8 +258,10 @@ func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - address := common.Address(slot.Bytes20()) - slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) + addr := common.Address(slot.Bytes20()) + // Quorum: get public/private state db based on addr + balance := getDualState(interpreter.evm, addr).GetBalance(addr) + slot.SetFromBig(balance) return nil, nil } @@ -341,7 +343,10 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.Address(slot.Bytes20())))) + addr := common.Address(slot.Bytes20()) + // Quorum: get public/private state db based on addr + slot.SetUint64(uint64(getDualState(interpreter.evm, addr).GetCodeSize(addr))) + return nil, nil } @@ -381,7 +386,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx uint64CodeOffset = 0xffffffffffffffff } addr := common.Address(a.Bytes20()) - codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + codeCopy := getData(getDualState(interpreter.evm, addr).GetCode(addr), uint64CodeOffset, length.Uint64()) + // Quorum: get public/private state db based on addr callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil @@ -416,10 +422,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() address := common.Address(slot.Bytes20()) - if interpreter.evm.StateDB.Empty(address) { + stateDB := getDualState(interpreter.evm, address) + if stateDB.Empty(address) { slot.Clear() } else { - slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + slot.SetBytes(stateDB.GetCodeHash(address).Bytes()) } return nil, nil } @@ -508,7 +515,8 @@ func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { loc := callContext.stack.peek() hash := common.Hash(loc.Bytes32()) - val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), hash) + // Quorum: get public/private state db based on addr + val := getDualState(interpreter.evm, callContext.contract.Address()).GetState(callContext.contract.Address(), hash) loc.SetBytes(val.Bytes()) return nil, nil } @@ -516,7 +524,8 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]b func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { loc := callContext.stack.pop() val := callContext.stack.pop() - interpreter.evm.StateDB.SetState(callContext.contract.Address(), + // Quorum: get public/private state db based on addr + getDualState(interpreter.evm, callContext.contract.Address()).SetState(callContext.contract.Address(), common.Hash(loc.Bytes32()), common.Hash(val.Bytes32())) return nil, nil } @@ -816,9 +825,11 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { beneficiary := callContext.stack.pop() - balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) - interpreter.evm.StateDB.AddBalance(common.Address(beneficiary.Bytes20()), balance) - interpreter.evm.StateDB.Suicide(callContext.contract.Address()) + // Quorum: get public/private state db based on addr + db := getDualState(interpreter.evm, callContext.contract.Address()) + balance := db.GetBalance(callContext.contract.Address()) + db.AddBalance(common.Address(beneficiary.Bytes20()), balance) + db.Suicide(callContext.contract.Address()) return nil, nil } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0b6fb1f486..ec6c3fd4c2 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -92,7 +92,7 @@ func init() { func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack = newstack() rstack = newReturnStack() pc = uint64(0) @@ -192,7 +192,7 @@ func TestSAR(t *testing.T) { func TestAddMod(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack = newstack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) pc = uint64(0) @@ -231,7 +231,7 @@ func TestAddMod(t *testing.T) { // getResult is a convenience function to generate the expected values func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() pc = uint64(0) interpreter = env.interpreter.(*EVMInterpreter) @@ -281,7 +281,7 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -515,7 +515,7 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) @@ -539,7 +539,7 @@ func TestOpMstore(t *testing.T) { func BenchmarkOpMstore(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) @@ -560,7 +560,7 @@ func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpSHA3(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) diff --git a/core/vm/interface.go b/core/vm/interface.go index dd401466ad..af0c373fbe 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -14,29 +14,71 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +//go:generate mockgen -source interface.go -destination mock_interface.go -package vm + package vm import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" ) +type AccountExtraDataStateGetter interface { + // Return nil for public contract + GetPrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) + GetManagedParties(addr common.Address) ([]string, error) +} + +type AccountExtraDataStateSetter interface { + SetPrivacyMetadata(addr common.Address, pm *state.PrivacyMetadata) + SetManagedParties(addr common.Address, managedParties []string) +} + +// Quorum uses a cut-down StateDB, MinimalApiState. We leave the methods in StateDB commented out so they'll produce a +// conflict when upstream changes. +type MinimalApiState interface { + AccountExtraDataStateGetter + + GetBalance(addr common.Address) *big.Int + SetBalance(addr common.Address, balance *big.Int) + GetCode(addr common.Address) []byte + GetState(a common.Address, b common.Hash) common.Hash + GetNonce(addr common.Address) uint64 + SetNonce(addr common.Address, nonce uint64) + SetCode(common.Address, []byte) + + // RLP-encoded of the state object in a given address + // Throw error if no state object is found + GetRLPEncodedStateObject(addr common.Address) ([]byte, error) + GetProof(common.Address) ([][]byte, error) + GetStorageProof(common.Address, common.Hash) ([][]byte, error) + StorageTrie(addr common.Address) state.Trie + Error() error + GetCodeHash(common.Address) common.Hash + SetState(common.Address, common.Hash, common.Hash) + SetStorage(addr common.Address, storage map[common.Hash]common.Hash) +} + // StateDB is an EVM database for full state querying. type StateDB interface { + MinimalApiState + AccountExtraDataStateSetter + CreateAccount(common.Address) SubBalance(common.Address, *big.Int) AddBalance(common.Address, *big.Int) - GetBalance(common.Address) *big.Int + //GetBalance(common.Address) *big.Int - GetNonce(common.Address) uint64 - SetNonce(common.Address, uint64) + //GetNonce(common.Address) uint64 + //SetNonce(common.Address, uint64) - GetCodeHash(common.Address) common.Hash - GetCode(common.Address) []byte - SetCode(common.Address, []byte) + //GetCodeHash(common.Address) common.Hash + //GetCode(common.Address) []byte + //SetCode(common.Address, []byte) GetCodeSize(common.Address) int AddRefund(uint64) @@ -44,8 +86,8 @@ type StateDB interface { GetRefund() uint64 GetCommittedState(common.Address, common.Hash) common.Hash - GetState(common.Address, common.Hash) common.Hash - SetState(common.Address, common.Hash, common.Hash) + //GetState(common.Address, common.Hash) common.Hash + //SetState(common.Address, common.Hash, common.Hash) Suicide(common.Address) bool HasSuicided(common.Address) bool diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 89feab0e2f..49069bb9b4 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,6 +17,7 @@ package vm import ( + "fmt" "hash" "sync/atomic" @@ -230,6 +231,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } + + 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 { + return nil, err + } // If the operation is valid, enforce and write restrictions if in.readOnly && in.evm.chainRules.IsByzantium { // If the interpreter is operating in readonly mode, make sure no diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index e287f0c7aa..e390b8903c 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -51,7 +51,8 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 } func TestStoreCapture(t *testing.T) { var ( - env = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{}) + db = &dummyStatedb{} + env = NewEVM(Context{}, db, db, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() diff --git a/core/vm/mock_interface.go b/core/vm/mock_interface.go new file mode 100644 index 0000000000..5060315167 --- /dev/null +++ b/core/vm/mock_interface.go @@ -0,0 +1,957 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interface.go + +// Package vm is a generated GoMock package. +package vm + +import ( + big "math/big" + reflect "reflect" + + common "github.com/ethereum/go-ethereum/common" + state "github.com/ethereum/go-ethereum/core/state" + types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" +) + +// MockAccountExtraDataStateGetter is a mock of AccountExtraDataStateGetter interface. +type MockAccountExtraDataStateGetter struct { + ctrl *gomock.Controller + recorder *MockAccountExtraDataStateGetterMockRecorder +} + +// MockAccountExtraDataStateGetterMockRecorder is the mock recorder for MockAccountExtraDataStateGetter. +type MockAccountExtraDataStateGetterMockRecorder struct { + mock *MockAccountExtraDataStateGetter +} + +// NewMockAccountExtraDataStateGetter creates a new mock instance. +func NewMockAccountExtraDataStateGetter(ctrl *gomock.Controller) *MockAccountExtraDataStateGetter { + mock := &MockAccountExtraDataStateGetter{ctrl: ctrl} + mock.recorder = &MockAccountExtraDataStateGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountExtraDataStateGetter) EXPECT() *MockAccountExtraDataStateGetterMockRecorder { + return m.recorder +} + +// GetPrivacyMetadata mocks base method. +func (m *MockAccountExtraDataStateGetter) GetPrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPrivacyMetadata", addr) + ret0, _ := ret[0].(*state.PrivacyMetadata) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPrivacyMetadata indicates an expected call of GetPrivacyMetadata. +func (mr *MockAccountExtraDataStateGetterMockRecorder) GetPrivacyMetadata(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivacyMetadata", reflect.TypeOf((*MockAccountExtraDataStateGetter)(nil).GetPrivacyMetadata), addr) +} + +// GetManagedParties mocks base method. +func (m *MockAccountExtraDataStateGetter) GetManagedParties(addr common.Address) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetManagedParties", addr) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetManagedParties indicates an expected call of GetManagedParties. +func (mr *MockAccountExtraDataStateGetterMockRecorder) GetManagedParties(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManagedParties", reflect.TypeOf((*MockAccountExtraDataStateGetter)(nil).GetManagedParties), addr) +} + +// MockAccountExtraDataStateSetter is a mock of AccountExtraDataStateSetter interface. +type MockAccountExtraDataStateSetter struct { + ctrl *gomock.Controller + recorder *MockAccountExtraDataStateSetterMockRecorder +} + +// MockAccountExtraDataStateSetterMockRecorder is the mock recorder for MockAccountExtraDataStateSetter. +type MockAccountExtraDataStateSetterMockRecorder struct { + mock *MockAccountExtraDataStateSetter +} + +// NewMockAccountExtraDataStateSetter creates a new mock instance. +func NewMockAccountExtraDataStateSetter(ctrl *gomock.Controller) *MockAccountExtraDataStateSetter { + mock := &MockAccountExtraDataStateSetter{ctrl: ctrl} + mock.recorder = &MockAccountExtraDataStateSetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountExtraDataStateSetter) EXPECT() *MockAccountExtraDataStateSetterMockRecorder { + return m.recorder +} + +// SetPrivacyMetadata mocks base method. +func (m *MockAccountExtraDataStateSetter) SetPrivacyMetadata(addr common.Address, pm *state.PrivacyMetadata) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetPrivacyMetadata", addr, pm) +} + +// SetPrivacyMetadata indicates an expected call of SetPrivacyMetadata. +func (mr *MockAccountExtraDataStateSetterMockRecorder) SetPrivacyMetadata(addr, pm interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPrivacyMetadata", reflect.TypeOf((*MockAccountExtraDataStateSetter)(nil).SetPrivacyMetadata), addr, pm) +} + +// SetManagedParties mocks base method. +func (m *MockAccountExtraDataStateSetter) SetManagedParties(addr common.Address, managedParties []string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetManagedParties", addr, managedParties) +} + +// SetManagedParties indicates an expected call of SetManagedParties. +func (mr *MockAccountExtraDataStateSetterMockRecorder) SetManagedParties(addr, managedParties interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetManagedParties", reflect.TypeOf((*MockAccountExtraDataStateSetter)(nil).SetManagedParties), addr, managedParties) +} + +// MockMinimalApiState is a mock of MinimalApiState interface. +type MockMinimalApiState struct { + ctrl *gomock.Controller + recorder *MockMinimalApiStateMockRecorder +} + +// MockMinimalApiStateMockRecorder is the mock recorder for MockMinimalApiState. +type MockMinimalApiStateMockRecorder struct { + mock *MockMinimalApiState +} + +// NewMockMinimalApiState creates a new mock instance. +func NewMockMinimalApiState(ctrl *gomock.Controller) *MockMinimalApiState { + mock := &MockMinimalApiState{ctrl: ctrl} + mock.recorder = &MockMinimalApiStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMinimalApiState) EXPECT() *MockMinimalApiStateMockRecorder { + return m.recorder +} + +// GetPrivacyMetadata mocks base method. +func (m *MockMinimalApiState) GetPrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPrivacyMetadata", addr) + ret0, _ := ret[0].(*state.PrivacyMetadata) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPrivacyMetadata indicates an expected call of GetPrivacyMetadata. +func (mr *MockMinimalApiStateMockRecorder) GetPrivacyMetadata(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivacyMetadata", reflect.TypeOf((*MockMinimalApiState)(nil).GetPrivacyMetadata), addr) +} + +// GetManagedParties mocks base method. +func (m *MockMinimalApiState) GetManagedParties(addr common.Address) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetManagedParties", addr) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetManagedParties indicates an expected call of GetManagedParties. +func (mr *MockMinimalApiStateMockRecorder) GetManagedParties(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManagedParties", reflect.TypeOf((*MockMinimalApiState)(nil).GetManagedParties), addr) +} + +// GetBalance mocks base method. +func (m *MockMinimalApiState) GetBalance(addr common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalance", addr) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetBalance indicates an expected call of GetBalance. +func (mr *MockMinimalApiStateMockRecorder) GetBalance(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalance", reflect.TypeOf((*MockMinimalApiState)(nil).GetBalance), addr) +} + +// SetBalance mocks base method. +func (m *MockMinimalApiState) SetBalance(addr common.Address, balance *big.Int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBalance", addr, balance) +} + +// SetBalance indicates an expected call of SetBalance. +func (mr *MockMinimalApiStateMockRecorder) SetBalance(addr, balance interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBalance", reflect.TypeOf((*MockMinimalApiState)(nil).SetBalance), addr, balance) +} + +// GetCode mocks base method. +func (m *MockMinimalApiState) GetCode(addr common.Address) []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCode", addr) + ret0, _ := ret[0].([]byte) + return ret0 +} + +// GetCode indicates an expected call of GetCode. +func (mr *MockMinimalApiStateMockRecorder) GetCode(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCode", reflect.TypeOf((*MockMinimalApiState)(nil).GetCode), addr) +} + +// GetState mocks base method. +func (m *MockMinimalApiState) GetState(a common.Address, b common.Hash) common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetState", a, b) + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetState indicates an expected call of GetState. +func (mr *MockMinimalApiStateMockRecorder) GetState(a, b interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockMinimalApiState)(nil).GetState), a, b) +} + +// GetNonce mocks base method. +func (m *MockMinimalApiState) GetNonce(addr common.Address) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNonce", addr) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetNonce indicates an expected call of GetNonce. +func (mr *MockMinimalApiStateMockRecorder) GetNonce(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNonce", reflect.TypeOf((*MockMinimalApiState)(nil).GetNonce), addr) +} + +// SetNonce mocks base method. +func (m *MockMinimalApiState) SetNonce(addr common.Address, nonce uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetNonce", addr, nonce) +} + +// SetNonce indicates an expected call of SetNonce. +func (mr *MockMinimalApiStateMockRecorder) SetNonce(addr, nonce interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNonce", reflect.TypeOf((*MockMinimalApiState)(nil).SetNonce), addr, nonce) +} + +// SetCode mocks base method. +func (m *MockMinimalApiState) SetCode(arg0 common.Address, arg1 []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetCode", arg0, arg1) +} + +// SetCode indicates an expected call of SetCode. +func (mr *MockMinimalApiStateMockRecorder) SetCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCode", reflect.TypeOf((*MockMinimalApiState)(nil).SetCode), arg0, arg1) +} + +// GetRLPEncodedStateObject mocks base method. +func (m *MockMinimalApiState) GetRLPEncodedStateObject(addr common.Address) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRLPEncodedStateObject", addr) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRLPEncodedStateObject indicates an expected call of GetRLPEncodedStateObject. +func (mr *MockMinimalApiStateMockRecorder) GetRLPEncodedStateObject(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRLPEncodedStateObject", reflect.TypeOf((*MockMinimalApiState)(nil).GetRLPEncodedStateObject), addr) +} + +// GetProof mocks base method. +func (m *MockMinimalApiState) GetProof(arg0 common.Address) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProof", arg0) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProof indicates an expected call of GetProof. +func (mr *MockMinimalApiStateMockRecorder) GetProof(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockMinimalApiState)(nil).GetProof), arg0) +} + +// GetStorageProof mocks base method. +func (m *MockMinimalApiState) GetStorageProof(arg0 common.Address, arg1 common.Hash) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStorageProof", arg0, arg1) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetStorageProof indicates an expected call of GetStorageProof. +func (mr *MockMinimalApiStateMockRecorder) GetStorageProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStorageProof", reflect.TypeOf((*MockMinimalApiState)(nil).GetStorageProof), arg0, arg1) +} + +// StorageTrie mocks base method. +func (m *MockMinimalApiState) StorageTrie(addr common.Address) state.Trie { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorageTrie", addr) + ret0, _ := ret[0].(state.Trie) + return ret0 +} + +// StorageTrie indicates an expected call of StorageTrie. +func (mr *MockMinimalApiStateMockRecorder) StorageTrie(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageTrie", reflect.TypeOf((*MockMinimalApiState)(nil).StorageTrie), addr) +} + +// Error mocks base method. +func (m *MockMinimalApiState) Error() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Error") + ret0, _ := ret[0].(error) + return ret0 +} + +// Error indicates an expected call of Error. +func (mr *MockMinimalApiStateMockRecorder) Error() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockMinimalApiState)(nil).Error)) +} + +// GetCodeHash mocks base method. +func (m *MockMinimalApiState) GetCodeHash(arg0 common.Address) common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCodeHash", arg0) + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetCodeHash indicates an expected call of GetCodeHash. +func (mr *MockMinimalApiStateMockRecorder) GetCodeHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCodeHash", reflect.TypeOf((*MockMinimalApiState)(nil).GetCodeHash), arg0) +} + +// SetState mocks base method. +func (m *MockMinimalApiState) SetState(arg0 common.Address, arg1, arg2 common.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetState", arg0, arg1, arg2) +} + +// SetState indicates an expected call of SetState. +func (mr *MockMinimalApiStateMockRecorder) SetState(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockMinimalApiState)(nil).SetState), arg0, arg1, arg2) +} + +// SetStorage mocks base method. +func (m *MockMinimalApiState) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetStorage", addr, storage) +} + +// SetStorage indicates an expected call of SetStorage. +func (mr *MockMinimalApiStateMockRecorder) SetStorage(addr, storage interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStorage", reflect.TypeOf((*MockMinimalApiState)(nil).SetStorage), addr, storage) +} + +// MockStateDB is a mock of StateDB interface. +type MockStateDB struct { + ctrl *gomock.Controller + recorder *MockStateDBMockRecorder +} + +// MockStateDBMockRecorder is the mock recorder for MockStateDB. +type MockStateDBMockRecorder struct { + mock *MockStateDB +} + +// NewMockStateDB creates a new mock instance. +func NewMockStateDB(ctrl *gomock.Controller) *MockStateDB { + mock := &MockStateDB{ctrl: ctrl} + mock.recorder = &MockStateDBMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStateDB) EXPECT() *MockStateDBMockRecorder { + return m.recorder +} + +// GetPrivacyMetadata mocks base method. +func (m *MockStateDB) GetPrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPrivacyMetadata", addr) + ret0, _ := ret[0].(*state.PrivacyMetadata) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPrivacyMetadata indicates an expected call of GetPrivacyMetadata. +func (mr *MockStateDBMockRecorder) GetPrivacyMetadata(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivacyMetadata", reflect.TypeOf((*MockStateDB)(nil).GetPrivacyMetadata), addr) +} + +// GetManagedParties mocks base method. +func (m *MockStateDB) GetManagedParties(addr common.Address) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetManagedParties", addr) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetManagedParties indicates an expected call of GetManagedParties. +func (mr *MockStateDBMockRecorder) GetManagedParties(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManagedParties", reflect.TypeOf((*MockStateDB)(nil).GetManagedParties), addr) +} + +// GetBalance mocks base method. +func (m *MockStateDB) GetBalance(addr common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalance", addr) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetBalance indicates an expected call of GetBalance. +func (mr *MockStateDBMockRecorder) GetBalance(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalance", reflect.TypeOf((*MockStateDB)(nil).GetBalance), addr) +} + +// SetBalance mocks base method. +func (m *MockStateDB) SetBalance(addr common.Address, balance *big.Int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBalance", addr, balance) +} + +// SetBalance indicates an expected call of SetBalance. +func (mr *MockStateDBMockRecorder) SetBalance(addr, balance interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBalance", reflect.TypeOf((*MockStateDB)(nil).SetBalance), addr, balance) +} + +// GetCode mocks base method. +func (m *MockStateDB) GetCode(addr common.Address) []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCode", addr) + ret0, _ := ret[0].([]byte) + return ret0 +} + +// GetCode indicates an expected call of GetCode. +func (mr *MockStateDBMockRecorder) GetCode(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCode", reflect.TypeOf((*MockStateDB)(nil).GetCode), addr) +} + +// GetState mocks base method. +func (m *MockStateDB) GetState(a common.Address, b common.Hash) common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetState", a, b) + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetState indicates an expected call of GetState. +func (mr *MockStateDBMockRecorder) GetState(a, b interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockStateDB)(nil).GetState), a, b) +} + +// GetNonce mocks base method. +func (m *MockStateDB) GetNonce(addr common.Address) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNonce", addr) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetNonce indicates an expected call of GetNonce. +func (mr *MockStateDBMockRecorder) GetNonce(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNonce", reflect.TypeOf((*MockStateDB)(nil).GetNonce), addr) +} + +// SetNonce mocks base method. +func (m *MockStateDB) SetNonce(addr common.Address, nonce uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetNonce", addr, nonce) +} + +// SetNonce indicates an expected call of SetNonce. +func (mr *MockStateDBMockRecorder) SetNonce(addr, nonce interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNonce", reflect.TypeOf((*MockStateDB)(nil).SetNonce), addr, nonce) +} + +// SetCode mocks base method. +func (m *MockStateDB) SetCode(arg0 common.Address, arg1 []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetCode", arg0, arg1) +} + +// SetCode indicates an expected call of SetCode. +func (mr *MockStateDBMockRecorder) SetCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCode", reflect.TypeOf((*MockStateDB)(nil).SetCode), arg0, arg1) +} + +// GetRLPEncodedStateObject mocks base method. +func (m *MockStateDB) GetRLPEncodedStateObject(addr common.Address) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRLPEncodedStateObject", addr) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRLPEncodedStateObject indicates an expected call of GetRLPEncodedStateObject. +func (mr *MockStateDBMockRecorder) GetRLPEncodedStateObject(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRLPEncodedStateObject", reflect.TypeOf((*MockStateDB)(nil).GetRLPEncodedStateObject), addr) +} + +// GetProof mocks base method. +func (m *MockStateDB) GetProof(arg0 common.Address) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProof", arg0) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProof indicates an expected call of GetProof. +func (mr *MockStateDBMockRecorder) GetProof(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockStateDB)(nil).GetProof), arg0) +} + +// GetStorageProof mocks base method. +func (m *MockStateDB) GetStorageProof(arg0 common.Address, arg1 common.Hash) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStorageProof", arg0, arg1) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetStorageProof indicates an expected call of GetStorageProof. +func (mr *MockStateDBMockRecorder) GetStorageProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStorageProof", reflect.TypeOf((*MockStateDB)(nil).GetStorageProof), arg0, arg1) +} + +// StorageTrie mocks base method. +func (m *MockStateDB) StorageTrie(addr common.Address) state.Trie { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorageTrie", addr) + ret0, _ := ret[0].(state.Trie) + return ret0 +} + +// StorageTrie indicates an expected call of StorageTrie. +func (mr *MockStateDBMockRecorder) StorageTrie(addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageTrie", reflect.TypeOf((*MockStateDB)(nil).StorageTrie), addr) +} + +// Error mocks base method. +func (m *MockStateDB) Error() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Error") + ret0, _ := ret[0].(error) + return ret0 +} + +// Error indicates an expected call of Error. +func (mr *MockStateDBMockRecorder) Error() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockStateDB)(nil).Error)) +} + +// GetCodeHash mocks base method. +func (m *MockStateDB) GetCodeHash(arg0 common.Address) common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCodeHash", arg0) + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetCodeHash indicates an expected call of GetCodeHash. +func (mr *MockStateDBMockRecorder) GetCodeHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCodeHash", reflect.TypeOf((*MockStateDB)(nil).GetCodeHash), arg0) +} + +// SetState mocks base method. +func (m *MockStateDB) SetState(arg0 common.Address, arg1, arg2 common.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetState", arg0, arg1, arg2) +} + +// SetState indicates an expected call of SetState. +func (mr *MockStateDBMockRecorder) SetState(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockStateDB)(nil).SetState), arg0, arg1, arg2) +} + +// SetStorage mocks base method. +func (m *MockStateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetStorage", addr, storage) +} + +// SetStorage indicates an expected call of SetStorage. +func (mr *MockStateDBMockRecorder) SetStorage(addr, storage interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStorage", reflect.TypeOf((*MockStateDB)(nil).SetStorage), addr, storage) +} + +// SetPrivacyMetadata mocks base method. +func (m *MockStateDB) SetPrivacyMetadata(addr common.Address, pm *state.PrivacyMetadata) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetPrivacyMetadata", addr, pm) +} + +// SetPrivacyMetadata indicates an expected call of SetPrivacyMetadata. +func (mr *MockStateDBMockRecorder) SetPrivacyMetadata(addr, pm interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPrivacyMetadata", reflect.TypeOf((*MockStateDB)(nil).SetPrivacyMetadata), addr, pm) +} + +// SetManagedParties mocks base method. +func (m *MockStateDB) SetManagedParties(addr common.Address, managedParties []string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetManagedParties", addr, managedParties) +} + +// SetManagedParties indicates an expected call of SetManagedParties. +func (mr *MockStateDBMockRecorder) SetManagedParties(addr, managedParties interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetManagedParties", reflect.TypeOf((*MockStateDB)(nil).SetManagedParties), addr, managedParties) +} + +// CreateAccount mocks base method. +func (m *MockStateDB) CreateAccount(arg0 common.Address) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CreateAccount", arg0) +} + +// CreateAccount indicates an expected call of CreateAccount. +func (mr *MockStateDBMockRecorder) CreateAccount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAccount", reflect.TypeOf((*MockStateDB)(nil).CreateAccount), arg0) +} + +// SubBalance mocks base method. +func (m *MockStateDB) SubBalance(arg0 common.Address, arg1 *big.Int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SubBalance", arg0, arg1) +} + +// SubBalance indicates an expected call of SubBalance. +func (mr *MockStateDBMockRecorder) SubBalance(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubBalance", reflect.TypeOf((*MockStateDB)(nil).SubBalance), arg0, arg1) +} + +// AddBalance mocks base method. +func (m *MockStateDB) AddBalance(arg0 common.Address, arg1 *big.Int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddBalance", arg0, arg1) +} + +// AddBalance indicates an expected call of AddBalance. +func (mr *MockStateDBMockRecorder) AddBalance(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBalance", reflect.TypeOf((*MockStateDB)(nil).AddBalance), arg0, arg1) +} + +// GetCodeSize mocks base method. +func (m *MockStateDB) GetCodeSize(arg0 common.Address) int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCodeSize", arg0) + ret0, _ := ret[0].(int) + return ret0 +} + +// GetCodeSize indicates an expected call of GetCodeSize. +func (mr *MockStateDBMockRecorder) GetCodeSize(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCodeSize", reflect.TypeOf((*MockStateDB)(nil).GetCodeSize), arg0) +} + +// AddRefund mocks base method. +func (m *MockStateDB) AddRefund(arg0 uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddRefund", arg0) +} + +// AddRefund indicates an expected call of AddRefund. +func (mr *MockStateDBMockRecorder) AddRefund(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRefund", reflect.TypeOf((*MockStateDB)(nil).AddRefund), arg0) +} + +// SubRefund mocks base method. +func (m *MockStateDB) SubRefund(arg0 uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SubRefund", arg0) +} + +// SubRefund indicates an expected call of SubRefund. +func (mr *MockStateDBMockRecorder) SubRefund(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubRefund", reflect.TypeOf((*MockStateDB)(nil).SubRefund), arg0) +} + +// GetRefund mocks base method. +func (m *MockStateDB) GetRefund() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRefund") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetRefund indicates an expected call of GetRefund. +func (mr *MockStateDBMockRecorder) GetRefund() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRefund", reflect.TypeOf((*MockStateDB)(nil).GetRefund)) +} + +// GetCommittedState mocks base method. +func (m *MockStateDB) GetCommittedState(arg0 common.Address, arg1 common.Hash) common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCommittedState", arg0, arg1) + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetCommittedState indicates an expected call of GetCommittedState. +func (mr *MockStateDBMockRecorder) GetCommittedState(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommittedState", reflect.TypeOf((*MockStateDB)(nil).GetCommittedState), arg0, arg1) +} + +// Suicide mocks base method. +func (m *MockStateDB) Suicide(arg0 common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Suicide", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Suicide indicates an expected call of Suicide. +func (mr *MockStateDBMockRecorder) Suicide(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Suicide", reflect.TypeOf((*MockStateDB)(nil).Suicide), arg0) +} + +// HasSuicided mocks base method. +func (m *MockStateDB) HasSuicided(arg0 common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasSuicided", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasSuicided indicates an expected call of HasSuicided. +func (mr *MockStateDBMockRecorder) HasSuicided(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasSuicided", reflect.TypeOf((*MockStateDB)(nil).HasSuicided), arg0) +} + +// Exist mocks base method. +func (m *MockStateDB) Exist(arg0 common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exist", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Exist indicates an expected call of Exist. +func (mr *MockStateDBMockRecorder) Exist(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exist", reflect.TypeOf((*MockStateDB)(nil).Exist), arg0) +} + +// Empty mocks base method. +func (m *MockStateDB) Empty(arg0 common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Empty", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Empty indicates an expected call of Empty. +func (mr *MockStateDBMockRecorder) Empty(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Empty", reflect.TypeOf((*MockStateDB)(nil).Empty), arg0) +} + +// RevertToSnapshot mocks base method. +func (m *MockStateDB) RevertToSnapshot(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RevertToSnapshot", arg0) +} + +// RevertToSnapshot indicates an expected call of RevertToSnapshot. +func (mr *MockStateDBMockRecorder) RevertToSnapshot(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevertToSnapshot", reflect.TypeOf((*MockStateDB)(nil).RevertToSnapshot), arg0) +} + +// Snapshot mocks base method. +func (m *MockStateDB) Snapshot() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Snapshot") + ret0, _ := ret[0].(int) + return ret0 +} + +// Snapshot indicates an expected call of Snapshot. +func (mr *MockStateDBMockRecorder) Snapshot() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Snapshot", reflect.TypeOf((*MockStateDB)(nil).Snapshot)) +} + +// AddLog mocks base method. +func (m *MockStateDB) AddLog(arg0 *types.Log) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddLog", arg0) +} + +// AddLog indicates an expected call of AddLog. +func (mr *MockStateDBMockRecorder) AddLog(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLog", reflect.TypeOf((*MockStateDB)(nil).AddLog), arg0) +} + +// AddPreimage mocks base method. +func (m *MockStateDB) AddPreimage(arg0 common.Hash, arg1 []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddPreimage", arg0, arg1) +} + +// AddPreimage indicates an expected call of AddPreimage. +func (mr *MockStateDBMockRecorder) AddPreimage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPreimage", reflect.TypeOf((*MockStateDB)(nil).AddPreimage), arg0, arg1) +} + +// ForEachStorage mocks base method. +func (m *MockStateDB) ForEachStorage(arg0 common.Address, arg1 func(common.Hash, common.Hash) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ForEachStorage", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ForEachStorage indicates an expected call of ForEachStorage. +func (mr *MockStateDBMockRecorder) ForEachStorage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForEachStorage", reflect.TypeOf((*MockStateDB)(nil).ForEachStorage), arg0, arg1) +} + +// MockCallContext is a mock of CallContext interface. +type MockCallContext struct { + ctrl *gomock.Controller + recorder *MockCallContextMockRecorder +} + +// MockCallContextMockRecorder is the mock recorder for MockCallContext. +type MockCallContextMockRecorder struct { + mock *MockCallContext +} + +// NewMockCallContext creates a new mock instance. +func NewMockCallContext(ctrl *gomock.Controller) *MockCallContext { + mock := &MockCallContext{ctrl: ctrl} + mock.recorder = &MockCallContextMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCallContext) EXPECT() *MockCallContextMockRecorder { + return m.recorder +} + +// Call mocks base method. +func (m *MockCallContext) Call(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Call", env, me, addr, data, gas, value) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Call indicates an expected call of Call. +func (mr *MockCallContextMockRecorder) Call(env, me, addr, data, gas, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockCallContext)(nil).Call), env, me, addr, data, gas, value) +} + +// CallCode mocks base method. +func (m *MockCallContext) CallCode(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CallCode", env, me, addr, data, gas, value) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CallCode indicates an expected call of CallCode. +func (mr *MockCallContextMockRecorder) CallCode(env, me, addr, data, gas, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallCode", reflect.TypeOf((*MockCallContext)(nil).CallCode), env, me, addr, data, gas, value) +} + +// DelegateCall mocks base method. +func (m *MockCallContext) DelegateCall(env *EVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DelegateCall", env, me, addr, data, gas) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DelegateCall indicates an expected call of DelegateCall. +func (mr *MockCallContextMockRecorder) DelegateCall(env, me, addr, data, gas interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelegateCall", reflect.TypeOf((*MockCallContext)(nil).DelegateCall), env, me, addr, data, gas) +} + +// Create mocks base method. +func (m *MockCallContext) Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", env, me, data, gas, value) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(common.Address) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Create indicates an expected call of Create. +func (mr *MockCallContextMockRecorder) Create(env, me, data, gas, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockCallContext)(nil).Create), env, me, data, gas, value) +} diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 38ee448904..30bd23e9fe 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -35,5 +35,5 @@ func NewEnv(cfg *Config) *vm.EVM { GasPrice: cfg.GasPrice, } - return vm.NewEVM(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig) + return vm.NewEVM(context, cfg.State, cfg.State, cfg.ChainConfig, cfg.EVMConfig) } diff --git a/core/vm/runtime/evm_privacy_test.go b/core/vm/runtime/evm_privacy_test.go new file mode 100644 index 0000000000..c1be7e5cb9 --- /dev/null +++ b/core/vm/runtime/evm_privacy_test.go @@ -0,0 +1,448 @@ +package runtime + +import ( + "fmt" + "math/big" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private/engine" + testifyassert "github.com/stretchr/testify/assert" +) + +/* +The following contracts are used as the samples. Bytecodes are compiled using solc 0.5.4 + +import "./C2.sol"; + +contract C1 { + + uint x; + + constructor(uint initVal) public { + x = initVal; + } + + function set(uint newValue) public returns (uint) { + x = newValue; + return x; + } + + function get() public view returns (uint) { + return x; + } + + function newContractC2(uint newValue) public { + C2 c = new C2(address(this)); + c.set(newValue); + } +} + +import "./C1.sol"; + +contract C2 { + + C1 c1; + + constructor(address _t) public { + c1 = C1(_t); + } + + function get() public view returns (uint result) { + return c1.get(); + } + + function set(uint _val) public { + c1.set(_val); + } + +} +*/ + +type contract struct { + abi abi.ABI + bytecode []byte + name string +} + +var ( + c1, c2 *contract + stubPrivateTx *types.Transaction +) + +func init() { + c1 = &contract{ + name: "c1", + abi: mustParse(c1AbiDefinition), + bytecode: common.Hex2Bytes("608060405234801561001057600080fd5b506040516020806105a88339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610550806100586000396000f3fe608060405260043610610051576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b1146100565780636d4ce63c146100a5578063d7139463146100d0575b600080fd5b34801561006257600080fd5b5061008f6004803603602081101561007957600080fd5b810190808035906020019092919050505061010b565b6040518082815260200191505060405180910390f35b3480156100b157600080fd5b506100ba61011e565b6040518082815260200191505060405180910390f35b3480156100dc57600080fd5b50610109600480360360208110156100f357600080fd5b8101908080359060200190929190505050610127565b005b6000816000819055506000549050919050565b60008054905090565b600030610132610212565b808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050604051809103906000f080158015610184573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff166360fe47b1836040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101f657600080fd5b505af115801561020a573d6000803e3d6000fd5b505050505050565b604051610302806102238339019056fe608060405234801561001057600080fd5b506040516020806103028339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610271806100916000396000f3fe608060405260043610610046576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b11461004b5780636d4ce63c14610086575b600080fd5b34801561005757600080fd5b506100846004803603602081101561006e57600080fd5b81019080803590602001909291905050506100b1565b005b34801561009257600080fd5b5061009b610180565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166360fe47b1826040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b15801561014157600080fd5b505af1158015610155573d6000803e3d6000fd5b505050506040513d602081101561016b57600080fd5b81019080805190602001909291905050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636d4ce63c6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801561020557600080fd5b505afa158015610219573d6000803e3d6000fd5b505050506040513d602081101561022f57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820a537f4c360ce5c6f55523298e314e6456e5c3e02c170563751dfda37d3aeddb30029a165627a7a7230582060396bfff29d2dfc5a9f4216bfba5e24d031d54fd4b26ebebde1a26c59df0c1e0029"), + } + c2 = &contract{ + name: "c2", + abi: mustParse(c2AbiDefinition), + bytecode: common.Hex2Bytes("608060405234801561001057600080fd5b506040516020806102f58339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610264806100916000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b1146100585780636d4ce63c14610086575b600080fd5b6100846004803603602081101561006e57600080fd5b81019080803590602001909291905050506100a4565b005b61008e610173565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166360fe47b1826040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b15801561013457600080fd5b505af1158015610148573d6000803e3d6000fd5b505050506040513d602081101561015e57600080fd5b81019080805190602001909291905050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636d4ce63c6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156101f857600080fd5b505afa15801561020c573d6000803e3d6000fd5b505050506040513d602081101561022257600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820dd8a5dcf693e1969289c444a282d0684a9760bac26f1e4e0139d46821ec1979b0029"), + } + log.PrintOrigins(true) + log.Root().SetHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(true))) +} + +func TestPrivacyEnhancements_CreateC1(t *testing.T) { + assert := testifyassert.New(t) + cfg := newConfig() + initialValue := int64(42) + var affectedContracts []common.Address + var getPrivacyMetadataFunc func(common.Address) (*state.PrivacyMetadata, error) + cfg.onAfterEVM = func(evm *vm.EVM) { + affectedContracts = evm.AffectedContracts() + getPrivacyMetadataFunc = evm.StateDB.GetPrivacyMetadata + } + stubPrivateTx = newTypicalPrivateTx(cfg) + stubPrivateTx.SetTxPrivacyMetadata(&types.PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStateValidation, + }) + + c1Address := createC1(assert, cfg, initialValue) + assert.Empty(affectedContracts, "Contract C1 creation doesn't affect any other contract") + pm, err := getPrivacyMetadataFunc(c1Address) + assert.NoError(err, "Privacy Metadata must exist") + assert.True(pm.PrivacyFlag.Has(engine.PrivacyFlagStateValidation), "PrivacyFlag must be set") + assert.Equal(common.BytesToEncryptedPayloadHash(stubPrivateTx.Data()), pm.CreationTxHash, "CreationTxHash must be set correctly") + + actualValue := callContractFunction(assert, cfg, c1, c1Address, "get") + assert.Equal(initialValue, actualValue) + assert.Len(affectedContracts, 1, "Calling C1.get() affects 1 contract") + assert.Equal(c1Address, affectedContracts[0], "Calling C1.get() affects C1 contract itself") +} + +func TestPrivacyEnhancements_CreateC2(t *testing.T) { + assert := testifyassert.New(t) + cfg := newConfig() + stubPrivateTx = nil + initialValue := int64(30) + + c1Address := createC1(assert, cfg, initialValue) + + var affectedContracts []common.Address + cfg.onAfterEVM = func(evm *vm.EVM) { + affectedContracts = evm.AffectedContracts() + } + c2Address := createC2(assert, cfg, c1Address) + assert.Empty(affectedContracts, "Contract C2 creation doesn't affect any other contract") + + actualValue := callContractFunction(assert, cfg, c2, c2Address, "get") + + assert.Equal(initialValue, actualValue) + assert.Len(affectedContracts, 2, "Calling C2.get() affects 2 contracts") + assert.Contains(affectedContracts, c1Address, "Calling C2.get() affects C1") + assert.Contains(affectedContracts, c2Address, "Calling C2.get() affects C2") +} + +func TestPrivacyEnhancements_CreateC2FromC1Function(t *testing.T) { + assert := testifyassert.New(t) + cfg := newConfig() + stubPrivateTx = nil + initialValue := int64(30) + newValue := int64(40) + + c1Address := createC1(assert, cfg, initialValue) + + var affectedContracts []common.Address + cfg.onAfterEVM = func(evm *vm.EVM) { + affectedContracts = evm.AffectedContracts() + } + callContractFunction(assert, cfg, c1, c1Address, "newContractC2", big.NewInt(newValue)) + + assert.Len(affectedContracts, 1, "Calling C1.newContractC2() affects 1 contract") + assert.Contains(affectedContracts, c1Address, "Calling C1.newContractC2() affects C1") +} + +func TestPrivacyEnhancements_CreateC1_StandardPrivate(t *testing.T) { + assert := testifyassert.New(t) + cfg := newConfig() + initialValue := int64(42) + var affectedContracts []common.Address + var getPrivacyMetadataFunc func(common.Address) (*state.PrivacyMetadata, error) + cfg.onAfterEVM = func(evm *vm.EVM) { + affectedContracts = evm.AffectedContracts() + getPrivacyMetadataFunc = evm.StateDB.GetPrivacyMetadata + } + stubPrivateTx = newTypicalPrivateTx(cfg) + stubPrivateTx.SetTxPrivacyMetadata(&types.PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStandardPrivate, + }) + + c1Address := createC1(assert, cfg, initialValue) + assert.Empty(affectedContracts, "Contract C1 creation doesn't affect any other contract") + _, err := getPrivacyMetadataFunc(c1Address) + assert.Error(err, "Privacy Metadata must not exist") + + actualValue := callContractFunction(assert, cfg, c1, c1Address, "get") + assert.Equal(initialValue, actualValue) + assert.Len(affectedContracts, 1, "Calling C1.get() affects 1 contract") + assert.Equal(c1Address, affectedContracts[0], "Calling C1.get() affects C1 contract itself") +} + +func callContractFunction(assert *testifyassert.Assertions, cfg *extendedConfig, c *contract, address common.Address, name string, args ...interface{}) int64 { + f := mustPack(assert, c, name, args...) + ret, _, err := call(address, f, cfg) + sig := fmt.Sprintf("%s.%s", c.name, name) + assert.NoError(err, "Execute %s", sig) + log.Debug(sig, "ret_hex", common.Bytes2Hex(ret)) + for len(ret) > 0 && ret[0] == 0 { + ret = ret[1:] + } + if len(ret) == 0 { + return -1 + } + actualValue, err := hexutil.DecodeBig(hexutil.Encode(ret)) + assert.NoError(err) + log.Debug(sig, "ret", actualValue) + return actualValue.Int64() +} + +func createC2(assert *testifyassert.Assertions, cfg *extendedConfig, c1Address common.Address) common.Address { + constructorCode := mustPack(assert, c2, "", c1Address) + + _, address, _, err := create(append(c2.bytecode, constructorCode...), cfg) + assert.NoError(err, "Create contract C2") + + log.Debug("Created C2", "address", address.Hex()) + return address +} + +func createC1(assert *testifyassert.Assertions, cfg *extendedConfig, initialValue int64) common.Address { + constructorCode := mustPack(assert, c1, "", big.NewInt(initialValue)) + + _, address, _, err := create(append(c1.bytecode, constructorCode...), cfg) + assert.NoError(err, "Create contract C1") + + log.Debug("Created C1", "address", address.Hex()) + return address +} + +func mustPack(assert *testifyassert.Assertions, c *contract, name string, args ...interface{}) []byte { + bytes, err := c.abi.Pack(name, args...) + assert.NoError(err, "Pack method") + return bytes +} + +func newConfig() *extendedConfig { + cfg := new(Config) + setDefaults(cfg) + cfg.Debug = true + database := rawdb.NewMemoryDatabase() + cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(database), nil) + privateState, _ := state.New(common.Hash{}, state.NewDatabase(database), nil) + + cfg.ChainConfig.IsQuorum = true + cfg.ChainConfig.ByzantiumBlock = big.NewInt(0) + return &extendedConfig{ + Config: cfg, + privateState: privateState, + } +} + +type extendedConfig struct { + *Config + privateState *state.StateDB + onAfterEVM func(evm *vm.EVM) +} + +func newEVM(cfg *extendedConfig) *vm.EVM { + context := vm.Context{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: func(uint64) common.Hash { return common.Hash{} }, + + Origin: cfg.Origin, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: cfg.GasLimit, + GasPrice: cfg.GasPrice, + } + evm := vm.NewEVM(context, cfg.State, cfg.privateState, cfg.ChainConfig, cfg.EVMConfig) + evm.SetCurrentTX(stubPrivateTx) + return evm +} + +func newTypicalPrivateTx(cfg *extendedConfig) *types.Transaction { + tx := types.NewTransaction(0, common.Address{}, cfg.Value, cfg.GasLimit, cfg.GasPrice, []byte("arbitrary payload")) + tx.SetPrivate() + return tx +} + +// Create executes the code using the EVM create method +func create(input []byte, cfg *extendedConfig) ([]byte, common.Address, uint64, error) { + var ( + vmenv = newEVM(cfg) + sender = vm.AccountRef(cfg.Origin) + ) + defer func() { + if cfg.onAfterEVM != nil { + cfg.onAfterEVM(vmenv) + } + }() + + // Call the code with the given configuration. + code, address, leftOverGas, err := vmenv.Create( + sender, + input, + cfg.GasLimit, + cfg.Value, + ) + return code, address, leftOverGas, err +} + +// Call executes the code given by the contract's address. It will return the +// EVM's return value or an error if it failed. +// +// Call, unlike Execute, requires a config and also requires the State field to +// be set. +func call(address common.Address, input []byte, cfg *extendedConfig) ([]byte, uint64, error) { + vmenv := newEVM(cfg) + defer func() { + if cfg.onAfterEVM != nil { + cfg.onAfterEVM(vmenv) + } + }() + + sender := cfg.State.GetOrNewStateObject(cfg.Origin) + // Call the code with the given configuration. + ret, leftOverGas, err := vmenv.Call( + sender, + address, + input, + cfg.GasLimit, + cfg.Value, + ) + + return ret, leftOverGas, err +} + +func mustParse(def string) abi.ABI { + abi, err := abi.JSON(strings.NewReader(def)) + if err != nil { + log.Error("Can't parse ABI def", "err", err) + os.Exit(1) + } + return abi +} + +const ( + c1AbiDefinition = ` +[ + { + "constant": false, + "inputs": [ + { + "name": "newValue", + "type": "uint256" + } + ], + "name": "set", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newValue", + "type": "uint256" + } + ], + "name": "newContractC2", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "initVal", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } +] +` + c2AbiDefinition = ` +[ + { + "constant": false, + "inputs": [ + { + "name": "_val", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "result", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "_t", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } +] +` +) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 108ee80e41..5979932603 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -17,6 +17,7 @@ package runtime import ( + "context" "fmt" "math/big" "os" @@ -24,6 +25,8 @@ import ( "testing" "time" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -246,6 +249,10 @@ func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { return fakeHeader(n, parentHash) } +func (d *dummyChain) SupportsMultitenancy(context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) { + return nil, false +} + // TestBlockhash tests the blockhash operation. It's a bit special, since it internally // requires access to a chain reader. func TestBlockhash(t *testing.T) { diff --git a/crypto/bn256/cloudflare/gfp_decl.go b/crypto/bn256/cloudflare/gfp_decl.go index fdea5c11a5..b718ceb8a2 100644 --- a/crypto/bn256/cloudflare/gfp_decl.go +++ b/crypto/bn256/cloudflare/gfp_decl.go @@ -4,7 +4,6 @@ package bn256 // This file contains forward declarations for the architecture-specific // assembly implementations of these functions, provided that they exist. - import ( "golang.org/x/sys/cpu" ) diff --git a/crypto/crypto.go b/crypto/crypto.go index a4a49136a8..c6da90cef3 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -257,7 +257,7 @@ func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool { return false } // Frontier: allow s to be in full N range - return r.Cmp(secp256k1N) < 0 && s.Cmp(secp256k1N) < 0 && (v == 0 || v == 1) + return r.Cmp(secp256k1N) < 0 && s.Cmp(secp256k1N) < 0 && (v == 0 || v == 1 || v == 10 || v == 11) } func PubkeyToAddress(p ecdsa.PublicKey) common.Address { diff --git a/crypto/secp256k1/go.mod b/crypto/secp256k1/go.mod new file mode 100644 index 0000000000..ad39a60062 --- /dev/null +++ b/crypto/secp256k1/go.mod @@ -0,0 +1,3 @@ +module github.com/ConsenSys/quorum/crypto/secp256k1 + +go 1.13 diff --git a/docs/Quorum Design.png b/docs/Quorum Design.png new file mode 100644 index 0000000000000000000000000000000000000000..f331ce581bfdd6b15349dd0737afd7bc1c138353 GIT binary patch literal 76981 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 literal 0 HcmV?d00001 diff --git a/docs/Quorum Whitepaper v0.2.pdf b/docs/Quorum Whitepaper v0.2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c2dac8f0b43f6ffe793c14ccd96aaab9b1fe0667 GIT binary patch literal 1887682 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 z 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

@12a0V*aT}C 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

?vP#LQ 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

jD80tEC{SadLT_?*alcEboc-ECrTncCJ+C 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 z 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

S;{p-W&msU4C>U^FV@;p4G(bsEKNioKsj3+wc4V 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 T1xI(YU-c;Baoyhrz|1}LXjNUq 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%0D-K#WbPtw2NWC&#TkF zpII503f70wCot)s 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 z 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

ASsm~XJPzDj%d88jnc^Z~PRy$^Y-Ii|x`k}iE0 zhx$4u6IYFH^o8o@gy*onWXi6ZdX5nbAXhdKCx?(S8^{BQ+lOEayug^VQeJoLYwSJ~ zI_xIq620?Um}SfHL5g(E>DC36Pm(@(c_L|bdh~rv=m4xx1m?YKwR3(!Z*ATj``6SJ z=D$w6h@t@o7VyS+g^A84tE(>uIQ~+s68$%^=)e1ZoSeXxcwBF#$nl~cen}x2_^ghX6qhTE6iTvs$aegZ52p2pRD+0djdr# z%iuf@@JLj2|LB%L9VeuD)Dc<1L^Qr?Mxsa4G6V>iibT8qEDuZ?Y^1bQz z9$)*yXDKem*^Ix6zIC#uEUIVzw4DW>e{%M$Ti+Y79WQBaE&BQ7D2>8qNoI<+tcM8$ zSIXT$X2NM$;r4Bzfrf;(j4%VU=nTq1&Y=7(NNKdmBWqp;=9!r;z7 zs0!kEA7coIN|xK;?LL%CY<#;&u{EUFZ)4SIZlagy)^};#6Q|}K+oz9C*99t8bgx-F zV90M)`BF(dY9*|KlL~`>qL5CITTi`v4-MfA(tx@h(`M93FI6rCOf8lPWLWmWNMlWI zmA(S2f9farPpqb-A1bShQ)X5_7Y+*?XKWo>%L~`*1^9N7FDTSrEc0PR9PyLkq2!K{vp{aslS1J{ql*5%1pYP621>iL{4Vo`8+H5Fu}4K zUcO*MiI~V9a;x6Y)oVl5{Liz+sWY8vBI0t~GFUbA=Ck{wR{H()f zMy{+rYq8yKf@t+mdW%#0hF&w&Dy!)yV}h@@QN!+i9`?5ioKkOir!p7K=yXdyN;ME) znLNWor!rUD0=Dh2fuMQCgVwQo##j#J2x2<)6_kF)*4EXUlM#B^zvl*}zxX62hRh>Y z{C>ul;U)3&o@aw2WW7@OA*P{Y?^}wpIIT6pyW#WsB7&FHDs&oghHG5Gr0eVG!TKd! zdE+Ft&68EovGPKb#=A_+(ag#Eay(~n7mu&??tG0& z2mHF-!?Mov#T+A@El*g_lV=*QVd6O_ww%RlmPIccs`_xg+)qo3sgRALP+gq9%i*bG zURD4jIXOuc@T`OB^u4}sk&2fSN!9Mg^|JCid+gGy=7jlUG+)p%_rhj^fzi1J_QTjZ zg*k056$ECtmKvCH>I%DEj2G71_CZ@OYhK=tH7!5(A=y-Ru~VN3?w^=?yD!nToPUa$ zw3nM!_s+>=o8DQ$sJf}BX+%=!dWmz<(8nt8S0w34Dsz=5MSDDKbW$md;d3eq@i{)&d#nZX6!#a7W z@TtxYH3eY-2LWYr4YAdWD>&v0+MfxQGlt}Db%foaa?Oack$ZY8&pdG<5YP2-onQWY zuBD0WvtJBX8_hBL5Z2>)kCWgUgCYxc_hD&uX_gXk4N?=l)^$>A*a>!7`tn>K-Cpr# zWb9rUm@B>8)e;VDab-+71NmpvABzkqJnL&?R-neCLhen9+*ISl^lxF2Yx+-9^cI@f z&%6q9)`qO`aD2h4LCz-jf&Bv_ruME)mk}yKo)<=5ld2g~8x|o9#+N}pgKOqL_?a?! zR->P%Xu~+zSHrHk^(FCXqoFd*4joTo5-_#*4D(V5k$2RRqrub0neP;Jj_^b6gFC$`$-KlS*_e7W_SIxI zM(N?@Gbp@J0PDiXHJWFOS#^3nXc|Hv zW=p%E(|uyylWd?N5!T=mI~F03DY4r79y{%}mjdQH0nF}_k*C;&*0&=d+oEC9;n5Vm z4iR-q!u9HVjppYT!pS&?u64?@j>)+7;n+_^q;5UV?`jt$t%bn_ZJwCX2?(#J-?M`i zthV{YcQ)!N3{BB9fkd*DLNoU}t}+bt!D?!uq4L+SWvc3{uHkAq&JO!Ez_*Ml(`krY z>_ja4qeHx3SfF2Wxjj8%MNN3>-Kc$wPItbd$JK$PXpOmw7FXtueaSm3Bt@_3BJq+l z%;!WYdU@TS!y|Lp6%QtQyi?Ei<`$8EOP#Y3Ixbfo6&U16{=~U8C!;NsYihJC;E55?&o*DW%JTCs! z`HecWg3HNvFZAC?Nv+@NOEjk-7Gx}_t~Bm$$2JSvTq&?9?Yo&2l~J=-$qr3vLP=WYW7_Pyo^k2fiQ zZI5b2CjXlLb0CXrEtrf6c)eai!>H|Iuo=uoUuUq+A+IB7$>YRH|Llq?ylL%udLNuJ zSO+mjV2No@bJ}yNu=hQ_%@)BXf@Tc)#x?oPgC4?XAd+BQ-^frfSe#l6>`c;wWqrB+ zQy`F}ID26=db4h!yOB4th)5Tg5ku7#nZ8+Wk7LcET!b`~_%v8pK5{!|8py|m&~nw6 zo@movr#Wq&mM(pd`w5zm5|M>IL!r~dt8Wt2y?tt#F(m8YB?iMuFY!uoa7g-GZdWZ* zpLd{tb-fqo@qHTF7y9#BTxLSrr427m3D7??o~j^5n;ZeV3&9N_;EK0@|61qZc2%21 z47+LZ@F}V83v2R4A?VpQOpb0=%@($&q$XIau)T!$DnuCqiAni53Mo(b#oMUyxuyla z2itD)qi-_q>}kRkIRRZX`TEOLpP5e3XhW8T)h9?ggTgGOLftfj9&fP?t2HaTL~wDj zJzxC>@wsd>xpyO_IXyYSIa9Xa@g=KH@>=v~DmW4Q*Z9jHMZZDJd|ZB>P%W7$F_N`! zJNAW_SyRXM4Ot|VEx`gwTUhqQmh~FJh0R#JApb#~vxW6s#M>Dh7`q{C5mI?@@f_xg z2E?oBR-pp{t7UAvB7w}SeZo{8NyA@CjLa}+roblYyG~a07S>VsKEiPhO5UjU!Yp)o zZl952dXsTq@{6NE84eH5uPSCM4od_@M27L8^_v&+@x405JGk`X*?49r>mzAt@{^p3 zAbo{c1>-V3k#^%FGq!+r;x}a?<1BC133K(U1vfg&7V4sDO3J!P-T9ve`Vsp+1KW&) zQP65T{VkP&Ot1qIq%e6OC!r~6kh54Rhu1OSfhA@e_u+m&dHD$9b5&Ehw8qwTD9eP^ z#jo36nF{<%d|Dj8o>tSD%T7$d$5Fs`4SbIYCwQVciH>%+@9nI1H${Gcu-)@f`%sky zB#n>Wqx9EM4Bt$?n5wTJ*?~b*-zGfzoU%4#tF);|Bk#q{152P&)UQA@b2>VUT>5iCdLm^(OF(+_aaV+#{7^ar#b!>7BWlzD;-X?z`jV>d|T;uxLd-Hs$&<1ZeBV-P~>9kM- zIb8glKe!&V-1pS75WA~7gC9wnX<>c)g)?1Q&P-K!-vfHn5R6IA1v&FgkyC0us$B_p zf}PRoFXoh4WJnq(3$tCgJV8k@>8OmqKw(n1uc$A1-bu1Eu1omRyZJsZOBreDcJA%N^JF#x{;A;E6+~shuIv$Jj`x4`v zLU-O#f)#D`QwA5(2ahev@$yz!`HGEpzN&DSL0IVmtB6(HI<89`Cc)5Fso;dTtOdY~ z>NErpLzN}fWP^6qy0|xuG5fg3mq?L&I3v*Do(p`<8wF_^RdI?h{0-LYis3k3^rN&`aN)mi30o>`b{;q&L2wP5zZ;H#j6| zBI?TLIq4U7YKsxZ75cP_qNJf@GCmTyZTu-G|9ucHiJ&(xv>@1^Bf6sw3|@eLncNZF)_sP!w+sNV7wi=tuocx z$3pNf$TC+g*0Qx(^N7foDbM&5gp~@VV02mi=(frjGSuuLDe=CAyqphTQ8Zubv!6&p z7q9_)s|%5CaKCY?2pvn?5t(AhWN(nyX6X>F?y!^!(MZWKOwR9Ct_wp2Q(Ua6=cBC% z4`n4OsLJdW30}S@j!*SClwK$TCOL_g$ydBOou8yH_e#{X`CwASayn-0xw5*}#h$2- zQe1Iu+K|@O^tYa8-sq%J_T_MV&HD;{v1`jYemgjjBwN3%9E90k!v>3JPRhEVT8I1Y zP^M8TQz4tm2H&a)VCjx!D+**EbGeA3v>dJP;$WvzGo35?_tl^%Kfbp zvO`+=hUsl95jVS90k(#WZ++qi8S5CK|IEAPM_ zb!=KG)KyFOqGwqz1gzYbI2}%*UU%lxl%NU@!RiK5Yzj5SUiz?xz^M4DvGsiNxg()8 zQ^3m53I%4XO)YTm;e^iJD#yEXEilRJ!VYgA%n3uaE75P)f4nTkTyQzYuE{)vb>?B- z>1}SrE@jcWe8ktwtzF??>=_d0ANuA3? z)%v_@dO?-L)4>#;-<8HMqq_Qujo}-*WLC*=D2zk%AI)`fpr~ zND~aRbeS0t_~z$)M&CP)tk+x`t_YJ6;O&-YSCQ$Ir(04DZE>~EAH$QBFa4a9o0Hwv z=%#_)wXbw#{c{`ZCrUUZrj{bB*HNmlfU*55f2Pc8OM;g9TI~d8K*ehL!-75K zob1g^?rvd@Gz+ry65HU=y+(`ETg3?ahZjTSp%Al-dv1P#2pvrwJpoLw0$hYq-Zu!5 z(z;mm%~d;CE-+b%lsSh#U7ZVJpzcQN(JBiWWC%SiCrejc?oZ)4#FI5EdS|*M$3sxV zeHW|cQ3qj2lec&fHFDt6*jh5F&_=+f82a2dh*776QPZB)%eVRIm5GBqISNT$qZs;{B~bq*Zer?t;x2{KwdSHdA%JOsOelP%GY%aE5!AXXuk4 z_6B(6K@0j`Zt(H@SZ_ye+3ejk;er*2D4`o(k-#a+WL}fPOW1wFL2O6_-?AJ>(rYmk z+%~S%Mx(6pDfcgv3ZV?UhUeDFEN3jyf4gjNtRN~odd?3g$?e>Nc>hbF5%5qkOKc5y z9F@uU>u7l0rZLcy<1)F|Yzc*AeXXWR5O) zSf2m9Il&Yfh@s(0JiD`_y>BmpYhL9Ti@%`&!T|0VDCk`$?YazE$m^U->6lvsCM#t? z)Js8P>*u}R)FNZ@w+q&|J(4k|MIPiYKBddS)`D74+5`Pij?)}^epraHp|vNSBxg+= zr3DtmTR+!E(-u7Zs3YysnN^2f)P^Rf4i$Y{xS5IBAW}YgpDJLCLa*cPrcX{sWCc?AAO>fDkd@!CFcY+5E#43R6 zUb@U6B?(cR6B#kD`yyPbWV<K%|=-Xxw$7v!6>Q zhj4ufL3{W9jR2Ad?T}e+%|&6(bQl?$BU!MD1G_nf&KSJNnMsJd#&~y4!6i(ut|4YY zKRkZ!%4{zTQ=F7aJ6BkBEM@EUsX#{67v>m>Ree{K600Ul5M;sd@S8mun@y%67B|!^W3aJ)Gw?5qc--eGliUt#?X`W@mpzx<*6}cU z4wYj{xT`kaGo~j>wWrJgbso!yUt;Ag*2_Wb^fG&+j}g0@++uj`Rg2q)2zRW5NV__^ zb%E*ZGNmyW$CPHDsh9Yui12M$|McPb!i|S46@xCZ1lQJ}{#}w@cjm(N{^;8s{INKG3KTDlsn>iGfI#WQpwuSvSl|1^giWCF=xd-ube3}rOA=ZTl8 zByBQ4z;szqjazJQzQe;$;o|+|DG@i; zcqW=cQF=6*Eqk@xhdyh_*}=Kdb6R_<~50LZV{X?S&Eke&y zFSe$oO%eI~H_WckusJ!a;G{@^T#mU-gmE44&V$|nW8~gw^H}yHytzg1y8?7BZr3Sb z-rm2u_k0VnyTx1Mf}>QLB?I(=z=~e#f7+>ryRW|tKizVk#70$LXS)e3G7zE2ubHaf z3xer7I*T;F=P*4`R@5|G%`X*T@27;fqw=6ygOJiXp0OD;->ZJD&I2^aOo6^EFM;Xu z4{fg{L0}j)@Ix9a0pX7DfF+lJ6p$a<-Mr^`{6^Wq@u(x^@7ucIes61o$SD@FL5e{a zblP{Hy<2p6MYA?T)0y4_J1$Cl&f2^HWTU+J_@jUExT#I{EhH^?t9EBhwSUaV`h9LN zq!t~J1`xBmZTTrav-V@m$AF#5r|0VVh45Uu`@+%9s2yTJ!4|Uj?u>s_v%gk1EYV@) ztdnbl6TAJ$`^%_AsZ6d_�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 z z-S}QRS^0>BT>EV4*%A6HGWU_+ObyCD^PGhB#!Avs>I&loyEJY;QITr4*Ecm>JR4>| zOeMaBcMr}H+d);T%!SI&zu{sVxCc#fq@RD?d^aTo9y8VgPL$gzT^@GyAS4~Tpy;@pkF&`1HlE6c{y)->Fp)K#U6gsu9FZOY4Gelxf423MaKqbjdao#R9@I z)wn6dvpAj92g;;^kxk0$m>!ZB+(!(6CCB8N!X><6H0fOURyfBXO%`20iWK6|Wpu#E z-?e&GFGe*ff=J-&iNaU$At!EJmn5Jf9r^5`Ml(A>WRRJQ4Oi(6N%kiC*nc%pfW7c^ zB(2R(55I=OCa;OzYEsd{UWt8K)cxVFgz+xo%ov5@`Uv#c6ASpl+M0O}au62#XuF67 zLsnqxi;{E2`)udUe?rWqK^D<+i*^6=t-PhwuQkfK#zW2a6timA;q%VGr=cd?gP(w@ zEB4HiMA|qhtBxOP%xS$nZ%kiid2koZI9hq4aRAmL(zq%JE4g+**(5%6;H_zd>$aqo+%bfF;RdX*) zW^h%guC}p{zLUNgBuOWio|TO+@I22e?QZwn29G7(;d96Xb)&HZHUqYUE#we%$QOG7 z=Ba_~K|oTX(u?Pz&wFchmXO@>^&kRODSt6xP`lW-}8C2r2cRkCaO zO$-`S;?9C_x3pzs?%5XVhTd<-d#2NJy*Fq({kFPZ;wV>l>G#ikZGw@~>Oh z`ZPX3ejqsiO0kMa_6)hGrlm9OMaL?Rz(pwMP?L{)U*iY5gN1$pgQXT>nXDEXOS(i9 z=_SF}sE1zt^6X5`gTCn6=LfXU#O;eesSzO9m-MS5>dmvOvU|*v({l)h>iL%6QU`!g zl}cW6ub7GLTU-JX3CaUT{f7758K#+S>LyT3tvXCrRgs#3_Ew^kQ4APrQ5OywUlYZs58qAr%HMt1ymXhCy$~tUWZ<6-m2>z3xv||L z>o{t5R%AT*W4X^0o1pAlS%3`|c z_{gs7<4$2npCpmTyA0{2a-3OyXc!=#ltY7zV4{j1ul3l~8QIDCLaL8>+2rZZ$amZ2 zkQj(6izm@F7x}>Q`=A)TG%P8fSUGLO>KKV zoRrQF$SS-9%rJPd@b=z^(&pKp-;CU3h#zV_BH*LN90U@=BKQ!o_KHMA>LY^+=n z>UL-_s9hFiNj6*a0|I8b=-M9;fC&X)iBF`d%dqcUzw zxC>w;=^F^^)S4GwxDa!uC@-CqlU-B|gw|9OsNHY{Q3G>1M_PiU`v|loJEWzGf0Ijr z?KALdOS(~NuyBQfK_MPj3eth&&H#f;U(o+bPvL5FVMmz4`#i_(b_QVr#H1uum8(1{ zEtI4O%reuH)LC?$cM&5eZ#>aB1+-pWMyt0>Ntu)B=Az`q9{%?gGOhvZFKyB`yUVs} z4LWs0KiXcpwC!2q+8LUZSntvpM#Dd1$SSOIhWG`!pPDyA z?tR1N7_K-CHaFQh!QQ5EIDD@6Is?aakZYHf^k-GZO)`Hg((c(V5Yu4Z>8R9lmHZ+; z)xKkd-sY`o!08`}$Si8SsSQ@hy~zVmDhj8#1FsG~h@T$%ThjHBNL;QT2Vksk6erN_DXpO^o>xO3Zj) z5i&ntSj*dF;8AoLWWK%gLG-Z@9@(%tf8CD%kGochT+_7eI$V@gOmoxB%iEO^j+Xl4 zcKDxt{*9u#`K|z z!KKI6AwX%3))||J${i6|*GYU36|QfMt_19bSN>}5p9M(K)rZg*Jpt2K#|#!$|7a>Y zDot2xjxd-lD9Hzz&8C);NZgC#Ekwg4(|WO!Tl=^?q7Y4fb%#?el#3mkcn)& z*K@_pc>@926o^`4yng!f@VR$IdhqLKpw#mjH6--NsBditchm;e z07o$c)tciJtyPY;-a(VDABJ*Dfl2Yz`e?U6xuA&L3*WCNZyemzF5SE^p5z=Owscyi zm*+sD5Lvj~z&-Gx$gK0T-_mE?6@x5Yrw;1DZy=L!zQFDI{hjQY>xhk)p4zqj0_A08 zp63zwRN!` zeOSz(yi@f%sv)litp0qAaLRRhHKJ5|`}-{t*G6c?-0RO?mfzBw#3v!JIuLrx*C8;R z8RJ5X3Vnfpw1Dm*@_>{?K{y zRz{rd4(qA|F;!Xy_Po6cd8x^#wvG;SW4qJ#llnYYO$F4ebam>_Pu(FYe+0QMNmXY0 z6lwLqx-{u!KO*r1?RJyredYRl^5Yls$eWrtrAkHnuUtj(Jd|P>R+xEW9xcg;oP0wk zo%pWVn=qk5!?>(0iPKU@s7Sv*IpA>!;{swrlmHT|eP2xLD`AZUX;XQFf`i`OMgWcG zYVSTVVr!v5BWd1=^!4|5HZjMXlN-u36MyiP=)ORP*HbC^tOr)!0y9-#9#whdCxl-e zZ>1?xUk|y^rR&_;Zp0QYF>MuoxCmJgy*1wU;F)K}z0b1F#?iO%2lE#KHqJbq2Y8nx zL-wk=(%{a! zD}Anj8U5wqmqe7`^MUWa#qjNq5`79=MNIXKrr~%h#6FO1W3{K_`z;}T*;)H$av zGaK*xsufMM_iD%_w3BRvdPe7Z#2M>A;V^O9JFlA^^rD0c4=I%U`JWr#)AlGMjeMX} zo7QB*;}FAfX`?H0RIC71(N~BH)MJyg7*Hc1KRX{*q?fNaMDnaZPC28{XZ+1ri!);9 zo6uui{B(c7hEU}0>ysX-4C~JfM7SG18NaYZ>qgYb@t;2}a$CBdhHChZvsrEzw9U%8 z9+S{IP1M4UFDJczcdtfva!=>>@O0mE(}~T2!KCF`TUHZilkgL#>|?H7gHW-JYSbpf zxh{$}e!#I*melPNY+k2nF)Z^DTOiM^<{e=M#3Ttzxn4;$Lc`yR?uPAR{it^V94+zH z1X63Sm7p;e3-}V3IHTX&Zw@>t8cToqLI%$3Y~c~Qm&AN8-jNgoIf@vj$M#!*orE$- zP3KrtNB=T-G;3Br-)k{rwDKJ^zA}qJT5dnnHrlL}rGITN^rCLespJ@Px9 zCV&N3V#01kE=YEYVk3jNcq6pX4o_bVV_(MSM8^|TY8{YB3N9PVXY{dq63TK?|2wE8BWm2yZ$s3Rak`k@DL2B zgcW044G2omG0}o=s$Miiy`0qhCN%sWWAfMycmEL>LN^_0yfmQN35QlQ5%R!NB=J(=wv)K2 zptvFiEym0p1e?4*5Cjf0O?j&#Dt+YBCy%RA9#eOC&pzK-%6L$?flq%P+H(w0ptGmT zw?2wDS6aI%c_DV&l1HMF{dhYFAI+E_m|$El~CL;rDZqvxv3sf z6)2`LYA*0r(Z>Q%lC-2USEg?6-+UjPN^G#~w#(AU)wEfH^wvhbPM|$V)MQJ+cVfrS z?Dl68+kTz8YAxqr#|?Y$vJmB$@kz%+#STCrakagN7@Q>#z+I8Wyrq*4zAj0Dr7%T! z!06B-J%P%eS8H@tdv~$%Jj}-7EL%mk(l0E;^VtcxdDknxc5D2Aoc1HV&L{?Q*I4Qg zON(@R9WOL0ZzjKWHqkt~*LqlrYMo?glzP?l+PP9Pi|<*Cv0Sr3rflJ;%Cbc{G$(Q* zy$Eo9JZaHyp%G&zqeGiJtJ($aXk;LfOSS6s8tN z9nWUbeD`!afOWZ@cDB!I$iE&>iT7d2|AhUG4wSw1eaqQWaVA*IaEbnK$pw{1{T@G) znaS0)R!YN9E8Hz#f8^Q=hbTL$8S|;eSS&PqTRpDb4HuAAU=6rbF%Ctqt{_)mad_Z8 zbMJ8OA7$^Z5plEM+IJ7oD8*wsfU-->EfgeU$buvuud*PB!%l7!mamDk(ySna>_4>) z;~PaFJqwIw!NJw%jZPM)$tcF+@k5z124=yinWZ2AFZLW5IyDk6-RsFErq9NI^qRxT zz5xCTrY|oT({cjZIL8jVbnMELfC*F(uq&gVE*-l@vQZ!UKEM|0NwpZZ``G(_lln__ zqP$nZP#a-_wa~sh8*hh-6su8KqJ80yxfL%ymReUc5#7Gj^_6V0bo(yD4K;6SezLjL z1JT}GrH|aRQE$m0Vg+BzM4I2cjj>+70c{r*QuIn{a1zut#hjlR?^5GvnSetkvy#;@ zI?mjzwoGwKOS{4N-t8<1|QbyGc{n~mr6JUb#8VUWn6!> zw^-IZ{48l+)kl@SGO=m?@!RUBOwWC+BhNQx8$PD~1cz*{STqQVGpW0q^9e`R>Wfn+ zp#(52hm+n#M!jd1a@*<71VcZ$={*wXwCPU=95%k~StZ4M^3G*0(2r|8+vHS>9cN@7 z(G>^vPz_=CG#MXN8#>51;VxqC3RB*t9s3*w&Tk-Y>JRzDGBkFNuWN&#>gWA{i08b9iHnA7z47%ZYnk|vMdUc~T;JXAI;+M0XEXSWs2^%rMyO|H z;1$?e8BSym(anWO@EU2Qoh?loT{n?@T)r87_hdJY@vyvuhwUBb7rtf@b*zWIJ;G4` z+l+~pHYt9q>w-ti=`22rpcP}Cu&DxNMID9rAplxb^8>WdP_fJI)TrLP0mv~;BAuxn zoE?>k^3|L_s~L;DnesA##0-QoJlB&h`kwBRE8r^sbxl;KU7+_lL)9~qqDep%k;LNg zY`8yXzI*|e-Ar#rFvxLc6B+Q~#{9ZpPPdLr4aQI$AA|n|Y3uea+KUD?I4gia--)vF zKx`bkTWf9^N@Fw74jo=d8^mKiZn^8vSivH zP%`Nf?J)EV-D^t9ViQ;U5omU0(fI-61>we#ro!};C&+g4&=mrNoG76JrCHe}sNLwP zn(lJtoVIRxa=YaN=G`BVZxilR%u-7V8Q7{;^@{UK%nf5B0@d_+LgW;Lrs_xOdx-Q$ zY-O&4{1-8gT z!=-_#T92yUIo#PTCy$r$bE!0#GX|_1O00~q*bnJsh@Odj1&8MXjnL?}!sm_+yQvJ( z4iB=dwWr@8V=_)MUOx=r_1v+aRurF$y7)OZg*gB(LYqHASZG#8@lAaqL%`msjLX;2q#GH~#^6TZ!$IKf@|{VMk}9vMid0H(I9zMr|o zG)dYGwXgVuo8``Jr}V3^PEnC_=!a^X*FF_E!74DxFGR&ZAuI5~^0YXZ@M_XtABk)~ zC2fJ9?J#s8@O)e+VJ5z9T328q;JnPMl9CUz)t;zC+y%RU&b%zbt0fcdW27n-7hJAO zeKa>7d*uGAz!u)kH2V4j)hD}vv&{8xV@8nT>n(PGlcojNZzI)E{DmWE(M_Xbls~TN z9|!9EM*%$lAG|-0+r9}B_p(kN`5qBRPDslCY@4Zm2B{cdafN}cjrpE-W8RH<EhI7?){am2#>V? zz_-4;^0^@Hw*59L_0Ji0TXh$##JbRAait;I+-7!vt^E}jpW|@as!!9~_1HsGq@(oY zje_ECy@1w~0L2pRLW!v_650BNR+Sk8z||uSA`*_c-haMkMM2*EC2Le_Wh6sD8%>^) zHB1^?ye(QoyCXq%#>Qi8wf{N%Z<%Th5zAYf-LqRf#~6C$A`o z48wnkxj1Uj4aVnG2TZWZ)ElNG2lm+l$e9Cv%etF3AetlS^hL6M(Z2cu3qrqmj{0pqClml(W6Wpn{@PUz67J4ppG)6JTBegC`P51$Er7- zmxJ|P$5~WfIYL;6(A_L2X&L$(6Ib-+c|QW^5zr=?MRQr?cM{-}sRJzBuDH6X1tU~3 z$o5i7+wE`%$e;bge$WIan$^jG5uE%MEqg{)qAFVEHiqnI5i8rJ{TiX8Ro|f=AohMsZ~6VGDV5chi9vOToEZn z=Y%!0S3C3{|3cJs6X^8HL>BFDO<>Ob*94-Jh|19 zmf~s;Hbm5jJhMc;d5Lt;Uo(P_^BW3cuGY-Xg!6b4)@rP_<5)EeF3mX*c&o&Evpw1F z=i@kC9V^KgT6d}oqGnFR*z5HK-LJSl-1UJrf2up3QKD<-gu)1Xx@^T~w~6cPpzPTF z?PiG5`fy~0qU&g_OrlSm7`1K;mg;dis_RzcT(-~HT_(G;d>;>_=xiA|BP^+P9AMHd zgNPA*Ln%6k`}jggrcfXM{mU*Pb0#qO`i4?IOihkQUnfvZxg902?)YPv6 zTWv%TCwCK!>?gGe5>@qz7$kor_HO5)o4|7PVc(wGlTLu4jFv#UnVhAo;U9PBIE22$ z#&}0A8S@6My9qFarjrX+ls-|V61|wY`(W-?k-X{2d3$Q-Q3lzy zgB~oD?S_Y*h8K0onOn$wCj&)(KqxqKpT4A6C3ugbo7g>g#Jbn-KIwiA>)j*a?rg-? z*ey|NIadqudA;9I(>lp^;Mclcq{8H-`{F?6l5uON4|Vi_mk3U>0Jq{)#|3r|3B0=n zmFu3uoPg-Yi0%2VA*}e*`xPR9|C0-%$dA2-uC(OgZc8sNmul`WTTu1v5TSzzj%mT( zPvEWwwB~E4J+0p$r81l}Z$A2*D{Pa+R}_1w1#oOgm2k2kw%DiEUv*o<58ArTEHME! z34jO6y*3JHtN#Ik6p5@y9z1gms5J?52Dr|^tUpn+*1#IjASWo%EXkP7`t?q_Dswsu zwt#W2QTS%w<#(3)#H+QOi4kre*WBLkeEtw+88licL5wXfbwO7>S>s}wT#~%FZTd7u z$+cS5O8$`4)zQ&!!cRxydZdXTsn5j&E>imYXZjjow9iJbqNnsMu8NIuG(&uHlHDBL zqHitg)O=dQ$(;ibdk*n7XrJ5f$9(O*tt&a+V04o&U6uxfQXbcnArb>cmy8CzdR#F{ z(5_Nb%7r~db`8<)3$Ru3TD$Y^$%-Q~>Sav|31rJ==<{QGw@aE&Arj&nP+%RdeuuZ;y=Q{95@ z$c054%U%;a6K(R!s6Ju~Xo(w-?X&bdzkY zVTZDVgSQo*v_OX*Ae)gXPOslX)Na^4s9yv#vs+JoP62OpuKe`W41bfiOz;}!732q` zUD~9Huvg8Gz+3Jyx6E1?>pRhv2eY;2)qWh8i`jwOPhDKw36c#R-%IMo~IwA4>MC(>M z=@Xi;*8QtBZnHft$!X6%nzV@Q1CEb1nN*SZy0Y6I8&2$IkuqU5ZRc8V69_pGFGD*Y z-kN%CzjB8*>QWJvs~+YsaUkiz4J&gvfkiT=u-$u79xX0!2Cc8uNIgg5^9Y6CNUMx4 zD>y8lkbg!WBjY1m9?VUsWdeVfejg9Gt#Q&6hsr)ti_ms)rs%_BiQfLah>S;9z1bpx z13q$5;sszSl9;A&#LXA+VJ3b+;0Y41arbKZ?tr;Sxqd$*s07*qkcLmiE`a5=QKFs= zU2)*t63y0K`2+GqZZ397k`eBMP}J=0);!ma-NZ@pYWdRv;k-gJ(KmJ5Y*3vX*JT?| zJ3nZ9z0}j2MpT2Q-)k2qiQGc@PSnfop*zFZnCsUkQYkjh>5Y+wHXhpGch!d;LO&jW zz2D_F;CLz0!vTNTRZKC{Q1s@3)^(P;w;DJqcZR#}v%;j$%Q{0%Mh_l7jx^DCRVe3_1|YQy}^r-NR5vcy)LZkaF3Fy zrJE&|Z$8F>Uegm5nArD?zaDo!Z```-azmgnUXY3T_@Vf&cAxS}Pg_CC_->Kv{3|~~ zb_$ZC(4wqt+=V?_Gq3H|gGi>G?y9xWE`9_nSEh*M)k?Zxi3#AUERSCl?g;JWPlM8k z2xajlV;!C7lx#}cR*aLzCu=-a>uT4_^FK0Qi}VzAhOcH~a8VWnAISlo!4F8dgT~mM zB-7wV_!N%1Y0N%Br3vjACMxYb2`#p_DZIyqPc`BtA|tGmioDcRc*6!i3^T}Ya#cb8 zyQljBth-eI(Vo^BGN+8ndL~6p;7)b8v(Y%CH3_B zu<(1|{+G*etf(Q-Tb_cW#rFYhkkN zT;st4YCo+d3eg0c35sHWhD_YOBNsKOkJf zxpzGjOfu+iCyjx=#C&90k2|{QhPSYH{~#Cyh>uWN7ucq4kd%X=uZ=i)OMJbE`-u7B zvJI|mQQ_D%cUz(?a}RJ?%{7c|oC4sp^}u1&cGL7#J|h)S`8xrteTSMjskeuuM%Y0Z z`D|o{n0?5FWURU%y*Y<~tco*(f$ALJc>)Y+d>e!b@&Q1mRqGDvt|ixct8^%k;hYu= z-w?WAyI%LE6?Sq2doNIo8q0Er=urM3uV}oML|_<0z?f6;0fVn#qxDHXmcCzbGJsk< zH{AZ(P4sxQBtuH7U@Wi-9SUB!qwZN z2?3MUDkJpyj{m~~X9dlpQ>B0lWo6k)9mLmP6D&qM)iPF^}TU7c^y!cUp3 zeQ*Q{enu_m@hxE-<)DlE0Y~VD>V-F^l(Mbqc3X!WO;v7pYo2G+6tyfG8gZt1r9$sF z`g7CXW$xG1At54+>b(6fO2(e0LOG`uae6i=Ts6V#~Vmw-@}&2@V{iC%Ufc z!iM5MILO-IjZwS`U?!xw?2K>>IS|RRt1|c~Oqg8jp32&ztJQPmuGRbRu+RMVI-8BO zVQ_78Y6n+IMxeL9?YZv8en6aoR>}7fTCehI$`93 zK~vC2A*$F3$D{R%Dv#MaTG*{ET}G+RZA6*0rdxJ;XHAbbEpThW{+m6^Hy$B@7TCE( zTEaR9LHRaA7Swg)__QX{YlE-eu_{H_(6rf!6ZX0-d8B!90ykwnd0A4Hs}&zb=@h~< z^uQJ7o?ChoQWJ5_j_Ik7(X4e{v|`?^X|Q z{Ed164%w0(_dDzU$Y1N}2VvUh9cS^;dHN3}dkp%-d*u`9(Gp<~9V(){Y0Vx~5K-dw zEg%@ftlVeyZ+K^Z?Z1K%XWQ4hF%_hHfS+ZQU$)XyH+zeurTn2)_Di|D9y0B(Cj(j6 zA5Qv_oF$W|sx(oZa6DHRB4RjNaHFr0SQ29;# zHlcUGb7a8V?KX$67Hq8ONVt0jjwYnt5ut|H48v}!TtpbEK8vH8_3)$^>x5a)u~S3X z@w@yP7s3gXcXEv6hxXDt@X9Z}!}G-IvNcV`pHF%+Qk~(F^cZd;d$e_6|?Rqj_$nTa~ZtJPEJN zfRN4Rc5f-uwoH94w!9H3ZS*v(#&!o=*O^23{hbif6wmhJ+4l~s#_bhCc90cvHUx*l z$E=)7z@hQyG`Ig@*Z=N>dO6dcF^%r&@@Fg7Yeu>UmlD7Ifat|9Q0m+WPMFi?QdU&# zx|Lj@^XBWla@PcPG|~$Y*@*m1w?9q=Q&N>Li>y~Pxn{wlet^9BzQ_6u+g;trpari=XxMhUKkOi1@*k{dTII9!4LlS5YaEr%m! zo3Ot|V*d9ggN^&M94^^S^VferH3VE{&?0Q)F0vr18%z%@jyNnz|3I$#k4Q)wZ$3pY zasXpjD{U3-=gxR;cCYmj3T=z>%#*zbQ++?^mf-UFi|adGIb;kM;?A6+mgY8~n%40G|mfMkExcRwSm)Bn+01#Ob_~wsDOK>83s?DZ^74#kkHl1mc)UwV!gARWnG4I`{H;fR z*9}t|pl2TheEPh3pMk~#9PNVQu|Qy5u&oOoJdIs2$u-dj9F}Cc-L)!k(^J+=&EE{~J8p7lO-E~sDrKRN*)@{>>t{lYT7$MP zzO9T^u41JWGU}$u{ON7eA0_S??=8Lg#O);G4Vd)gEqr;#$#krfRCgyj@LV08{zX=m z_xFW5+-Xe`?XwNJuBEq8k1Xk`8nB=BgHaf**QS>Njt_Wpm^7;1F{tnLMU?-i>t9B|< z#+B!K1T+roo*QPBu;wV;Y;8Asr3!N=+CZ9EP4$wGZl+g&vNG@82m$JQ^#b_(1Jko_ zw?{6Bxr}{`FAo|mozG$6mHs*jAZcHuIc*~a0Yb|U$k}FYO1$}suf{j_Cv+S zx7Q(C8E{GSr8?>mBTlQR98O$xPENE9_mur7k-kB8ytguxZ5ozjxy2}z{fX`PnfAMH zy?rYOp{R^Np*DYdeVH5b)?AcxC$~{mgJ=*fDbK9+zC}h!2BOpZpkKWNc3((mO>d9) z5pqj^P%mp(*^OWA#9T_h1jQs`~=n;E&}1$D_m%$77Wki{#sGb1nJ47jAaF0Om+^Q;&A;F}Hig z*yVxIjEdLkWH6a^tMcb=JS=h5Q&lohhRu~ zQ~noDuJHL+rdI`|zG}YCBD_#J;U!R^lb~D;Q&lcDzS(8}bw;wqt`ER03Cl6Z$K&TM z+uGz1vE_M@wBQzUbvG(Js7Nas3)tE8Y47c;I4OH*X`GH88ft`{Z#`)aVaTf}Z`kQu z85kGzLBP#i2agm9mYd3P`ofCw1zYwkBEdp0;W54Xi!O@VPwhraD=s8_JdyHBl5vl`5~!%AEGC(zA8~?@m7`3r$aVuGT>%1?hc7MD`x_!?53@;u2x(vQrFc~ zoJc*~H1KIn$)tIr_$RT?&R8eC^0H<)Ev}VZVhGTa=3-#UeKib@`6I}t7<)6&^!fBt zT*!n^eP+^8wF{d=jjj}OqUIl{X91B4y?tk60W@q>`$f7%3cix<`o^chnX9&UgH${a z%QjgTY0jJ-O4qYEMU*WL2ZDC3?>6;Vdy5*3PwU0KE2%{e z_YwqjB$`Jni?BN70Y@}~9$RXJSHmWJxv8BpbhZU54qj&jf*fzfswbKQ{d@&j#&)gB z%@A+4uez!-5a;qSvO?tQd7T&UG+$1t5}C_> zeS}5hOMa&67h@E)gT6pBGXhyELJfzqSfWCxu22Hxqz@ggRb!8k+tu-@`XtfDq?CaL zx%xaWAH2ETrVd9;UUtU6JxrJfw2RMbyc)(WIm(q)3iagcr`ay_5BX(RbLH5?2W-OO~H1y&nnKw+>{Ux{2q8`Unxx4V4#`HjvDOm zZNMX7GBA|*j&=M}=B7$GFuzgyl!D@LwXX}nY>{Nzkrpi6MNp%dZDu7GWv|(8Y2rsz zVLh#!oi+-y@#)7kFJqmp=<{b?KBzdU8mzEqUA$ECwxT%nN{!+pPbx7=O-L@AX+yG# zO8sMqH!r+?8U})Umr(~C_mTRyJ41)rzdv^Nhnk7%GVS**8h9Rf$>8R*_z*n@cvco} zC8enjV-o^sW>)$00wkGlvNM7REMfi(Z_(xh^XKYogweH>cujy~6J+88j_M^x>GiS% zcoxC=VjK%lXo%4G8FIxB{i58270e&Gk`>Y0lz4_JCC1`?KL>6HrRIs8?3^JxkB&2# zU{wrb-zTOg<(4r#)V|i$b{u#g7e?`l-&9iYeSfzM%YVY2E*X_B!|+%*&Yco(ZVZHi zY*PhDciFo~lF3oF-H;nC`~eATBIjL%zFJrR^7MijJ54-Oqc4B7-WPcyp{rF(B0>+$ z^wK5M&p2y#Ne1WJ(b}Uj9lDfhW5$|%wW~OSnkpI%Z#0EJf$Vgcbc>Q(WRG|+k9wBq zs>@t@2M?oNd49VnJ$wAwwKlM-nYr$bJ>n`$ByYMa60seZMq1d{_iQ@Q_1QKG4VTn? zTU_zxu|@E;YS|l{tpK2aQWO70U(LDu`9?J1-yir)B5nLHhTPFTC%$s1?9e4o<{&{h zB$rQzVfzA9gzX4okH$#`ktf5}An+f}>J1 zjoz&SW0h|i2!Y{%aH{rS^79uMQkN&YFT1WB5$|MeF~=l}iq}^6XnCdkRio>e3+BXM zwtfHOuK(TO#AGpW_Sft!7^VXslKW5Vdo>S$Vb2(xJkhle2TaC^vJxBXL;sMm^2wjV z7y8v*aj*V{yYVZTN^e0);eTUUDLdT*cpJ6kp`TE#a^~Q#kI*%A-T+HY2p4Z%oG=@1 z2GID={PFl4`V)4y%I7`CzGASnhtV0qHIu=U5_dJ5J9@g|ccbbZlJ7&#&Uu{wu&!Uu zpdUE1dAK@o9r1~BhFE~5`eJCV>mk3T4MC|G34Y^LV7H(6q$tHE_HlgG2hqw?%$G z18l0UACLmmY)8Xa@#2~D8SwYLf2_;V=edV6y9uSG0oYB04h;2o@X;Cfq^zd?$+N_k z4qy_z5%`l`0Bi&HKO)xJ+hrb-*2O=c=)`{anSZ+e?O7ea+txoi{--DZ3$iTW{=#jS zxzL&(JhBI@(^Tyep}S7?@4xd6X8barZt!j^;O2At)hGA_4S`Uv<}=bR0))8wBY2Zo zZF158rtT`1gRA#&{BHdJ=s!t;bmChF1KQsEmkk`-P)Lbsa|wgdi0&IdDM0_edH*}$ z3E`t(c+ugc^R|jKGZGuXdh_3p(}dToE-Vl^Sl2T%X;Mk$1BW2}a)<1$d+RDyig9qc z4H@5*;OV-FG{}eoVHm$7ju(s$3@x7X_Ef6mYzB!-785IcD#>5UA3^sfOFJkvfa?hC zY5rn^{)^Xmqycm2;Z$16$VyWB?e{z;?jdHb(T#+cc`qdg#Q&3rnF=%!RG7 z6NWCWZ1}OdrKh4N+Svt0h_5KFFj~nE7Qn5TRt(ox%a{f$#zyQGa2*$SiDu-!W#GmS z^8K;`;%;3To|Pzbtgq?~up0|-FUZ~JHyvh*XR;385822Zl>06nWBkQpUuS`%&P0p)*iR3ujdfKyGs5k@@KWdFJ67* znY(^i;CG15JTAys;#eH7f0n`6E%Ax|-&w>fSJ4thT1jbL`y-J-6Vd7#nA^|JpyTE) zY~4}e=GKe0(+#86)(ld?_0{2)bfqjD&y_~9lT-byRz8FL*uA)a)=Sx-O_|e)t}l+C zS3hnUDs8zdz{y|B?`PX>nP$ioJa0w*N2>68CmN^re^7*Ob#3L#o1acm_5T#^TlaZ& zZkK$#g2(cetkE|@l$=Eca2Zkqv?GPQ;Dn>K1ifORJ7pb*qr7}siglbMfA4X+4&kJD zYUvUPs>Gx*0idtVcD9r$U%#ob(aJe-&3e)&+H0OLhSEg5A`^MNTcm?UJb4c(AxQX~ z=#|Y+p7fiewag+mHD%w46jyfgnr}vs#2E|61pCtWAFJulrn>!r{8Z#)X=saszEx$d zPHFuQf1>3p{{zDAqEz|$csRWN*(It!m0kSj;33=u#D1oC_oVlGIATn#8h))f zj~Rsx=9e{2C+X_G^c+bUe-e#?MT^k9u7er1{D1IHbPANgUdj#o}1`%mSNf|l>28kj4El~G1_Br1<=iBF;?|=O_ z*Cozc!&>XD^*-8<pKdQ(^P|xb{qC>oYysw zORmlxIuo&N{$+)h`4WJC`*KD)4`0I#`zZ_{ig-rWH|NQmIetrI?8*iNxpt*j+3|Q( zkG9^bFGb*&@J*30b(}Woz?Y~^!q+Iy3g+KiLy?^EHsn9wziYalxc_jdb4RsT6 zRZvLG<0)#y0DHP0*oObB!Tr)BAVHC$qEsX0depVDNvf8Sp+Ttx+L6cY25n< zR|?=(!FKsQr>1Bc*K59*nes$%v?_jc;3?vLI>3_j3)K-Cx%P?R__9VL8Bm$r;^Oa* zN&>-H&w6Ghferj0#LJf@EA^x+PO*o$u~f^_et z1BK5ZyGH+--YD2u=g|`u@HE$mCm+xhcH1TD8o*SW-3JIKKmtTo*M(zya6j<;Kp$Cb7se)4nG^%p=Ko0ct5*kn{iuS86`*N|xTP)+yVEygB7(gQunyz*AzU)O ztkMb!;ibxm+rN;AKl2oe>)6&`QU1a(pMAQE0(5*E$KpXoV3?Z zRnPw(rPg2H{j2=xe_i)SW9Kh=x4%#C_7&x|y!s_H5mRYeE)qhi+|zt2EkZ(Gby)F7 zU@w3x^`~q~h!v3b^OXlY`ZEYf6q-F+9$mVpTOJ<$eNH8WeG_~zy91Fs;{wk;(QW%= zN7?!80AQhh^LN7&zvo)Ifw72L!xDj+^jGAZ;z-kMZ)0o-5Ry1>AN&5`Yka~9xa`;T zL@a|?t}dA@E3xAhaG2Bc2Ih2v;&KtKxf@O)H9NeT)7O(slbAZNa%^`PWAEn@6uFNb z4{WCcS}IsHW^DPpc#u8PB?}@7<|1&H(&F(aUG>(~ojppKQ{I~&vn^jB`dqG@Y-~xU z0r0kA*Po|C6z$*fUCvm0@`Kff^7}+MqqX!OsY#w^mi=Kk3jnyE>By;m%dxbiBu>_U zJoOdhmA{+{Ur3dy(d=>7I?b<*dh-1u++?*0zQm+d!+P2E>FAWiOA}i0RaIj8Zf+4X z#-Kc=vW)n#;`4&B%$_7y-|OJ1;etN>BTD2>5Oe*az) zpPy|$sHfNVdTM|{)$?P+aLdKh0CxRp06XLP%*GD>aCz3l?oX=LXZT?ApBIh))h)!o ziVt4rjvG|buYWBHVUKSE?0>-!_!ODCjk@g3nA!Sp2#Kt%rsm^_N_ zPmtgyn8;LRiI^;~4LB1^HG@;E6Cx;Szp66Hw9<>Ne}@nzo*(P(V>g|H=PDh;sq2$Pr_pZdUxKaw4Vefk05F9* zvih57C;)`;MnOdMrvgKMi$XrzJ=x97w`q<)70`)b@T7VMCY;0nl7YX|4;6xiS2k@x z?+8=B{cA_@M=RIA&i&uq(frvviU2@9RRzz;H3Hzc-~foA8BVBAx8-+F{Yu|(OcQ^& z@cRkJB>z*^h`as;_LQYn3>mFkgK@uNT`qsk4be6E%Fj}j4kuQ9w|wIZ9ZSLJS50#E ziGfq73)K(HMhA*h4h9Z&c_90|Y3|~1_DinxBaMR;J*M}9z$AKA)^^XT6U~FrAMlc4 zSf6|}kaTu;3uwNgb}AA6MSxHjBix?y6OoA4DggNblz`^IzXO;CzG0RYe?cJe{q72S z`8#}RO21V?cGeUqJ*P zKdS8Mf`7y?+4sSOCRjIx@9yS((Y^-cjg`Q)3;=)S-QH1v6{fo&z`wR>5b~Yc9`4B% zHZTc4K&?!c^*JIT*wyYH#s@0uUgL;l(>tsM((UT3;I?aB$ z_4C#S>1*?1e14B^^Taz9TOv4V-wc9CN^fk)tnsMob1BFUrX}&-RE@> z_AQ_|CLm)F@PqVu93%$9#=L-a;XF3hMXXCW*ti5Vgamka1UIfykF20#mp0=o4Q$eI7}`HNk4ZpC zL`-s>mX4l*k&}y?hnJ6E?2fpEq?ELbvWlvjx`w8fk+F%XnYo3fgQJtPi>sTv-{U9# z0f9lmPoraEDuo=wSeD$ST9_lUdX7ZD5w~x^+G~+IjuM@DjF3V z`sG`S7zTD%uCe=`!@C`oUfO(~nnP(5-_U*llYoYE24LC&s&-bhf2?AU{-v7zR+^rEP5$(F8ToZQMFqt$m_Zyb^~_yph;^gpv^?xDC-*zyYMT^oJNm>_-U4;|Mz=o{5P(vC!)Q&LSL6N zN^x&SBj83WZVw^GUxk$+dqbw#mSTugUvg`Ij4wXr?cqIt=Xi2qYl7H zw{%J&S3|iUdWyXU1JG+nwIvhzJixbE)BE$5^FJBgLV9&1c{n=K^*8l{Um_x0E*#-R z3l)3;U7uR87?l)!4Q*B6guaEe*jjvXUaD;OAwJ1x>-0xwo^$T??OkpKVU% zHa8A}i}RGzA7ONJ zH;P||={dkp1po~m0;w(M&!5a>zI!8-CAXUTo%vAify9TFwDy{6Pww;3>NpkW4?UIR z;OpfMpRHg9RLM@CAaYtRu87iv-14BoQ(?!mN1PRbqQl_hHOClrfQRbr0bj~c`piQr z`8s|t;K~K+0wjTaDa4v7#UP#Zsn*$VcjfdT4k!;AfPPiswd^~)cNxUyWaMTvQM;8) zQpS2$KIdqF!MBtl`%SAHdR1-;M)|V>m@d7ZFNiArsaULkr~dy$qcRb;nU`tKbVz2a zeyziZ?6Jy}{(l})$R=m2+ zRi`;3E%&Zky1MYinGt-jb?&|!4i&mpM095)6Z>&;HH{jJw2ke(&=vhC9q{j#vHqk_ zm9R?AzOV`-+md3#-<|x2jA`mJeux zJTAm9!D*@90 z)314Ly0wVMi!ofyh{wGi1>HBpUU(IfY|mwuE}86HTM=?66#Q+m^~{xaN@a8)k^4e; zPJ?j9)3V_rk9#G_kA4XiPXWH8o`2kTbm~Jw(LTkOa&dUgD5T^hUOCk$y!umA2FOPH zAxiVt@n7xnr;UL>nMmrkEwC@{LU!M>*;y541AOXd%8}ufi#73|L0AVV(L@+6ql^8R z6&dfv?68fAxwO9HY;QQnowwwws=C@$cN}YV{92@}7+j;iR8%uR;hC5I{i)0p-PTyV zv+pH$RUvzf$9N7s)%F#$W;uFaxkVjo^B9_z6KknJo?O^>^gW<xDQ(S76g zPqnJzS%Wo+lWnb&{lVBFtE{rXuVam4Q!noop3+u$Tf>F%dz@YSwfy@W)2E9-jQ+bJ zaKMD^=EC{4*Zzn5|3hQb$dccwUhU26>T6}&0_N*O4Pz#ujou z3gNh1DIq_gF8lsnRM!`*OTl1V`-1}g%kL9Pw$?`xI?Dy-bji_B8qb81f$!kRquW_y zv2Rvr68S~`ovnFR_!6*ARru600h^B%y<_V%-8TSoiE8Oj%+g7!39_vP z^^s|0J0<=#pFwA6X*^ZLyX7M)oxNcGvCp6;U97JpXm^FF{#w=iX%_dd)!e^-HTxf4 z)x=UB1q0-fx3q*$$uKnmZ%$XC?KXbqDfDfc@4&@tbs^ZHpH)r*s2i98&eUk~>SUH`?UWKjD<3RyZV>gE;EF%K z$+e`V`@V4qR!xo8VW&s?9Uy}Ko^rIknK%0reg&Q2b|47@v6*_@s>dL5urD$t^7aoj zYVwQwhgilWr9XTI?R2i{JtsVnoMC#`Ds#wUz8aSxp5e0)P1<{t`Ku{$LZBi ziUDM?UOF7iNOh3>QiI3~-|7!JV1c?O;iUUqungfz5_i0proGe<(jYXN2T z$F#hjeum2PgKewz_&!Fi zJ&4eZ*^|`ZR~p2QsrW@^4D>A+zWSTV`@&Nu{msDrgFIzl)z`9_6N)Keox@u*dfhX9 z`^ehhIz8_13>&7v%b_>I=i+t%ZNo1Xjic!_FSNRcD2_I)_GWX5^$~tfsp)>1z2(ZS z9;0}Sz$bY7wMkWeK(;7lvHOEFB53g^kF~tB3s&mTk$HyclK{v8Cl4r zzSK_E#Z%Ox03_vpd{O`Npl@r~q~$KTmbS6yodz^_xo`Z?FZ0IKC2cTqlg6Gl`ltGF z3FKFpJeg*T>Vil@Zk%#PpJEBcxyR}VxN)DziffCOc(jvSgQtOo)CQgtK(pjs70~wZ zPueqOzuLi1F6x47n{xO8G|e@g2d(Nvd`yvxW=3VNcJS`I2hTQ+JA4KSVNqHGJGm79 z!uP_k-N3bnLgLr$+4OW0REV)TbaiN+siair#kk3YmIwnWwDk030}4|U%-LB_tBCaI zQK;LdX#B*gsHCj?2CVu@oOG?)2~pOXp=JuXc<-zi;gW%U&!hu+^euOEd1NXE(^aZM znS6VUim6i6k9}db2Y_miSE5r~Lm|R)uHCVyb0EwERb zEZ4AR-)^DFbD{~3U{GRkVZNT%quvs>I`g+osr~haP9YstTh1S5Igv&bWm;|ZV&5YC z+5z1_L)yzdlj0ypUQYF7Akf(h-Uk#AZ^7kKN{J?5d=cWDXklXvqP-4Ud%;bmC7A)Q zC+mTG0I`+u{MlzWu1bWtbhQdtl9P5Stw1v-w5@0P(y#}EXG;X_?~gnQ@@wSKb-e7> ze=gxodSs=N>L0iqZwaFL*hfcK&y-Ydo_td${B z+V5}D7(~V(4H^c28b&VTvbS1uhK@%d6~5~=jtd|d>uK%IKN)5m7lUafQyosE>DwaRS)8Swg3b2s-cfV`z%oA3#DrgI+q_dC6wXwyIhIrVz_5 zD`HrvS;4SGb-s*j@Q^>M-3>8V(T9*a`q;N?Jyma5zD-HoQ#2uHIBu?(G!zgfJqmt# zl!E9*;jeDSr56%ih^uM3?3%t!MSN)kXR3T#NRumsD_P<^Sum>kfTVj1oGuW0>+wz9 zwR>!$q~&U+z6HXirYsa9tkk@DPLRv*Ep7_9`DW|Sq+unIFuet^0H#ex2dcdsFftbx zS^PF(rIc;#PCE@@mf`s!$4>JA;fe}8xV(+&6F+)lSba|i1rtw)HZB2jlBq8_t1rt! zgKu_1&y{yj4R>f1!l=DdBJ|eW#znit9XbBhkGocOp*D+nSaI~tMF3DcF7XEH1`f|2 z6EFoo0aCF(EsI9n$*76IM!X%@a3JrX@_w~flKP094`8a^6_Jr)@Ld$99dsHG6KzP3 zY+u4=B0R)s>pXD>s3}bw0{QR?79!VOg<4lCp@w6c*_zTFJZc$B-q#(~DQrfGDf#j} z?@GfHDR_6cKf&~?nk;O@ArtoTb zSJkL+3*5lr(J<0G#o*bU2#V(1+U#1lww+GPY%};(xre2_7;!~*iO8KE;$jv~Xloa^ z>H?Jb+WBEHQVlK5iEF;YL&$d(gFiroY0j~v@LC_WDf>)Skm+CUkt*1&Q%SnAEafbO z9-NzN8CM&_xG6;(*p2Uk{_JrkdTGUX1EECugaDZJ`m(PIT@Iev?hCY=oV=lq>n7s? zwYoSkafjxKAH#+yPDWIMqAXua_|9vqDc-8VY}|1q4wp_#&?=j_+Zy=F1*!Cp&iR6= zG9R~lWyY0#jeVPG?CFuM^6wHr7(*km+;$_1CM|_&99^jJYtV#G2R4rahFa6gFh%u3+>`wno??Kedp;%FrjN$2mz+?@95*|dsgD$9g=}XP zrFB3U&RoIgSr>U#=%uK}Dt)?4^~C^2cap^;IhgXyadm|E3TTPG8IX{$woKHhn7VKt~3-GecpbLV&l;jSEhQz~mseZyH#pfQ_U;k@#%g)BRRs2dvVADz1tcxyilh4YBr z*qVfciNMod&T^Pr4K_G(jUs7y7!YPw;j-3)N>^IYMq;2m>C*x=oi%_FI+eA(0Lj3B zpa*)U%iQEuU-{b>XyLDe?-)mkNw_d9i5s{+ZNtjF8u_;H zJP<)>>3smuiR9v;MTljiaD}8Hf}vW0mzqsQTIJ#BRmTCArVLGEK79J*t5962%DB0F z8+N9Y@_~BEhnH2@*XT-SRh+^cSKNbjIklaWV0A!mF7?rHOHA7mq5CG=1ff=rxl2Bj zl@51MUnU+gx-4}&pP05r$fc8eOs9oOfldf68eWB`p2FJJaRWu`gA3QYWs=F1V&rlq z9L{icA$k;r>OZ2Kxbc7obhrRLQqJ0K?&b;3f|6DH!v4<#2;5FgbAxnMO;%VQ!h z*Hkw|$;F8ZNOB-n*{}LIeR#<*Vbid)M&f8r!x77<0Tj9hr_~+EKHNU`-O!b~8tNBm z4$6p1rdHazsX{JYBA^A|Ch;2ZA3^=#f`{}Tuz~G}#CCt{hXRQo9gDS~suh|Bk%ZK- zeKMd1Kr#cAk#D&N=()#XIaE1MjRa)4zGlA1V4QqMmi#JzlA;dQbXycwPbXH>v*3rM zqg9?FupVm@(a6BLlaC-rPNk@3p~BcEc*Hv=lTtQgN?00p&hwkJlP$+E-VzaY__o^R z8$caPS3fG7Q?SKrJ{3O{?)T$MyI82pq@#~ZoSeEg-gHqtEl*g>4@w4QzzDlL);)BT zu1mfuFs&wK_6f;fjL3~*`_AX*<9O- z+2a}$zv0k96LdP6U}r?VH@dw=M!%sGS2tVe#cqdR7S!kIoQ7S8r=BVfzfoTRv96GA z7LFZXK6uiYiQJ9i-?iYwtf-+!14mg>47jwRmD*Yhv#Ol356c2r&WpwAiFr$`+VpPd zURDWGe&fvB4swi96=K|}xWJV>WMGh_`uaYevWYDVEm&s$KD?kizsG{cLrnyh1(@lF zwE=A^l{MyR0llgl+KnV|)WmIOYO#4=zy51k96~SShK2Q*9)v`vU4YOQSm!3PgGL`kbV}{G5Osa~37fY1f3IE>7;hMWa{dfvTaC5()q0{pm zpVpTc#lT2bnvG;igi{7Ly~tA8s+#M^kbQT|)VJfsq#ds(ZT zz!{#TMv*=%W>Ux;ADxtB>cej(Zd@TAz2Vy{t0>8nU{2Smn52F)g;TjuD7v#k8Igxl zlu_*^3%jPJ^9*PP#o^ITe@{u4#-a`!W;GvvOg0rKYb~hEZbM1-V!>~h)2l*yFH99J3G;@i)@;$;Ixi%& zbb;In&j}RnpI%pARqC5v6M4-yMeZhTJ5dSxDoo0MW{}BiDHo~1VpIfo6t;XV1&5jF zLsjjnD;pym8N3fAiy2KPHF8quE>2Wu{L+;E_p=iOYijSW(Gf4HsJV*!YV=EDheN7;#=)U1}~Mc?+@594mjx zJB5Dx07~FZ+jx(eH%&5qKu0(MjxEqr;SXbFAVq{ZN6KTpF_a?XPXRR8{tbN2e;Spt za^~Ui7{KovdHIy%%Mu{_%2N1wXZ*L0)w!FtxGA$8V|yV-VFAnP*jYX4gMj-5jcDrk ztV?hp5GLqX78E+ks1=zXh&Za}0sN(l+&}x}r~s?^y)4(zB)GHgnCaONc#d{jYbw6w zO>QlJ^I%-0WA?8(=cpy}r?loHfbQToxvt_RPYG3LepwvEr-)@W%Zh*_x6bWCuFxvZ zQ%!WKA3Q`lou_%1BSTQOS;#&L7sG>Ib1;E$Zan6$JT|!N^7!!>{1wx?BVLE}ad6#V zSqpzyYn6%Ni1tjdGE~ffN z+W2b*cK2y*ql{LI_yca2^d3r2sIxgbUg6%8tEk)AC<}P6KBWe67w!hGV_d{?qPUQp zGe|x0z6Ht$r!_&N?t3F=iy&cTl@{}RJxJW*3OSJg5a8|&VgOr4G zQ#aJ}08sB1R>bjuD0@?o$Q%w`KQt9_~kGgT$mFSe6Qn-u+Ix2>r_6dfMcv?qme%kp6zjCGL-fww~h#^97 ziU5%SE+o4LZM61mV+|e+4#GmdH2ol{UV9i@8z%EXH6Z zcpd<}*NH}u57)q50WVpDP3MVV=7IWCOOY)eoW0f2wWitEBH1XWBEa5d4jTxC&U=%A zJ7+(G8g$ivI5iHO61{RHci63Ya;cwYw6bBD7Ile94CXQwv01!L=TtV?*UzIRI9M+M}qYpvc0p8pJ@#!FQ`T0*ILxoH{` z7fkn9u+EKcw0=+X#YqcvAdOddnjU{`GGeek6%Z060c38FDB`p3LP>O#nx?L**7gCS z+ef|z2bldiSJNK4n+DS(N%!3seiRz&HL$OwD2neSzmL&-vJ7F{j2Qsr*|aQ>S=MtJ z1eAep_iS2Egok*sFRF~>_z%zQk%&o_SLWs}o(Kc1Q`q^rjB$r5o*vL=sYq7fPeVIE z3IbX&u=A83?nj;ChGSYrUmyiPLkSIDtH-}TO2Vs7bb~`vn0%8{t>F5wutjsPb6z_2 zyG+bXWFNujouS=@g>+NJ8xO5M;Wkl+k1|7v2GFx){6;P}l zJ;=Gw%VpidX6hw8k-YI4RH0YOdgRf)4WZNDCj);_L7GHBL8fI} ziq>l73z?(D4scpE;aSQ#*aMzZngjK;+(+9^p-kGyqN{qkS*`&pL(E5Y*9C-bM{7}T zCIY^MN$}x^LFA1PPx>+0kkyL?41MHvpgbwB{1 zTVxu4mAeT(k8zk0`Tn}Y6tL}xypymE_^zi(!Ce5)39{fuORY-c$cHE0TbJg7x0eLz zGGBG|fTkK&V-Xg_Sk#Bm^pqUmm*Mb{-U+qagRf~#1U(xHMVe2rq|+>Rq(TVi&`Ubi zN0>Pa)e@JO?mxtGYv>H;vS6X%HowESe>-1L0mDJ$aXFDff}uw9x>avQ;I%G)VJFNkD=WRViRpI2un7d8Rxz&2E53Af|nj6qwGxAk|jz+wSvT{NK>2hqTl0$i< z2h@r~_|dbh`zTF^SX-a;&jVHt?Z&{Lre`@uRYOKh0BKRKFy$N8&b1 zCnB}sOP@g&o&$#p=!^dKbRHS{#9CehU>B~TmA%W_7F)8Iah?=tDuaTuCPm4WAN`iB zl+>l7Qgj|rl%L!3q84j_R#YZ#(P>l}FEpT>T$;ouWVX49L0hAb>-v81w^6V0{m4c) zcZG@%7Ja-^>lsW#u0k?~aB`y=vkSj{k9VQkTJqNLOzEN55P29Nc?4g!}ySs z$`~b7o$15eFlU2Z@m$bR=qz2fyBDd^3blflooI=P)qz}Gy5%Pw$(mQN`k~eX>*CQS znk%%1SUyZ6JFNQ{fbm+;BLs$IivC_6@iOkRbE0+3qrsbg)^6DWN8wl}JMInBNAA)m zAKfHUf$7-~+#pJkoTqz%`No@jH!J3LN2`SCN2fiaiEIb!K7+`l1`y`4ys#IIM6ZZx zqZ`dpGAcJsBk&l@UJ<{VgVN37oGbP@Gzoa^mxQ_9+FhX4#v~liYZr;vPaU_3UUPkd z(j>WxfAf4n(-qhEniQz^DYQ?y4(pnVt&Z+}2E~D&ps))P&omb{CD=Z$#a(y4J_>{?F7HxK_SH0qJj$;|qnrW2hQ7|-S(kBkl zQNc^zbl^N0&>}GAHa-T4>0CC@8yR1rxHy>dTK?LlGRB>PCLANxAwTS#+V|jL>FqQe zvQ2~+QyYw3s@1T;2G%jIT#3BQQ;4}0GQCfV&bz+~eQ>&j*fU3WqYHBEkrlG%y((84 z*NneJLKncL-d@DONd^Lu=(8W*!!EatM}@0zl*Oy5 zOU6*$oC`i70@@@{?TscwF*hD~5@t)9hX_(-QAg|8wl38l+pF7139XJq2O*lLL@y;vfUmOTEV(=H2(AV_5hYccnU~v#WJYZljr-^3 z9$G!F3}0|O!hG4XLWfjlfw2@B~X;p9VP6W_pfTI$yu~@TeDW+Em zFp5$5L%?VjY-2Z+T1a&4M_ySSs7#TL#JRGU+H^q-=n`kWX*!CtnQ*%{L`hF5h9z>d ziY8m235fRzPA`lOCktO79OsrobJOr&yH>a$;TjqY<7C?3^E$h!=B-Zv7t@nC1!eJY*2B#NeO zmZ=@o#|OXBTi}|`JhUKgxv?Iy>`kfvGDqL^CexEmxtEswYgJ0f>Mp3z++G@mJ6bvq z6sLRyb8q*va`hVIHU_n6mx-x+dXi^k%olfn=AhRhh$%(4dGjS?$VC;Wq69&0$VA6| z#?K%TO2>Y*X_BQelTcZffJ+oRLp2+jVlivUJDJwlZe5c^BgisG0&T z-ZV$%U5;edSzD-Su|PDAJ)ti0Vfq4X2G$5-BCL;#vMt_p1QsE}?2x!YahByBTKjJhcYau+X`#mbV(B+^ld$t_`E2n=655z=4QW6r-( z=ISq%&dbit3$Xy4v=Ko5|5NP_W4LdpSyo5*wB)AgbMj!)>Z;eRG>PvWEBeb9&GD`w zsSgDjQLc^RgwfzGUA}CNy9giGbFI?xd`YBQ4Xo|75KPkHsG8QT)ntiVy`)-AgFU49 zbV9FaE1G6Ffv^VPT-j^R;;?E@xez{e#VMENRZ=9A;TcI8E;p$REZwyJDQ&e(Gh_00qx!ima=neW2XOlm-@(YLu?(iANGZJ3T&t zX5-W|^?Fl-J6pWWFY~9O0UOmz;5r?VK#@7ih~t%YPr#mEy>;^O;nY>YuosA&8g>As zx^62Sx$75Rwm%3*!V)u!X=LCJ|6?%XRG5mDhE-7*H zl9stM%jVTP$R&5!Y%c+ktTI6zm=L<57d`HxJo#b>*MewUOL6C5-JuBTvZ8iIFUSQY zT2C``m2gZa{M2f_GX>VY-?zL+yZ8BE0SCKWmfoaD#!kRA#i75fdC(Pr!qp=OJ9c(W zoRDM`VeYu`p4BeRMb-$tFG!<=U$(K2FO=MJ&L;;3azxH+`_2_M8l!c2;~!rDqtr3 z4B`i^Z%?CJ1evB@h1@J~}e|bZuH=O$rGpIqeYpREc2m}6d3OmrLb`DT8zz}n8PCY%SKm^#~E=K7k z2@+;qFd%;H=Hs<8FH9zpW9GdNkvpLl=;G4dRl=^GaE`{i4t)TK72BuUxI-HkBs3@Mei}M+$KbA*+|od2%WI zFcVm^m0ZOo+Rc#3n0^fF5?AcO1<(yVv8x+e6a)h$<=VVx!#(CFDX({D|LJB z;E!93B{vpgB-^}-kO5WwE$Q~Z<(K_Wp8xax$EsYUOXaH))z1Y8OPDU4T=C)=Sf!(> z;(0>ctH(GGXk7w*ke1`>aV#!bFp<3;6OZfFv8;4@>o&Xu6{CKs4esq`?xm7(K&ZZ+{*M1|W{nj# z>_mNo`aqbrAyv7`%9c0E@($>IrZ9e zyOWPJ=3_Rpmz*!UeorZOP{+tj`2OjSxgr-O|o!a(L8l5lLz9P!w1R645U zX6??`uUCTNmHp&5z0>h$UzdifsFmhZn$CLNc4W+V;9YV$^|GFBL%#p_pC=3d2KQ^Q ztWWJ=>vGx;468hVl@*pw`ga4*t86iYeZV-k|?|j>AOFi8+k9%%LV-0#eO69}1toTrTp?D~Q zRqb6y@M{7O>k5Y)C2+Dk6z1+bmWaX712~te)K;`@>F|Bi5mT#@6$HJ6&j`cKQ3gDf zZ#-NYBqqYRb|6Rs(C>sYFpB8E2Y@)UV+GGV zHHZ;tg`-91JkGt|VjHX9>pY-Y<*lz$Qn8mR(2|cRaa`rBGWYDQH}2;shqn#1W>)Tk zq6TXs{EsF}ceoO~$5MkX%f4@ww9LG!7T;o zp4YW8qgEb0X({rU9z|9YD3y5MVNQMPtEMeB=^Y@gM?~w}xu#bECyYFMbo>41cGf$W z0hwUPK9)G(w&^q4hFpga8EJ(;w1}t&1x0|nYj{5@XA)?gSbA}cX5J4#pU)I_k$lmw$05$0()z`S@r65GqAwpxNQX z4p^3!;it^LDOlJ+xy2?1JoH{P@RaJtNA=K-8yJD~EUj8+3#9 z$>2F)`{=7)BnvFxw!E0SV*`$8HJT5nVs1(ni=Bpp7WHw9!W!Cm?67ZjUppo2KEGWA zCU{r85XONUr`V3nTWqVzBTP^gM9R*Mgg@kpAx|*I8wI`0!v|pY3sDlC)z;kRP>}`08w4iP+5ObF{fFIbQ5n+d``i&^3|r@|vsUQV^!{ z`o_(;jOI+HC+3k=mWkh^c0Ug{L zfq-+-wOL7454K*V$>nNCIJ7I9Bc#_It~vhj_&kKIg4HwC$5bz?)!Otgmt4NCR(Vmh!yKi)iC-`E zeB1(2tYn2)mZiIChWxHOJ#{_`{({R`cs^`3GP0wEZ5TUtsgYnnS!yWC8Kykkk3M-S zrz6u~0r;>%Za6{UIeYoNc6T7Q?RM*A2^djK;CAB%SG%Lt(PHpy2hN^2Rhi79r!CO$ zZo;tx>U^_)VW<9UwnkELA-LFOx?KmORqc%}wAf{m#K$cDNIFqscs9W9oQJyp3^VOp z@@sTkN)iTdC9J4B$MB#lEX7cVTmzl;uvzT}0u*Z+fnB&CuFfaX*@W}@!U-@*XzS!tcc!_*{0-*TFL z208lz?VpM`j9%3($%Y9D$fb7IdKG!7U)h{_>3T#_@p85at1+v|qEhE|a)3L!BfQxF z{NQ@{xk+PkJDvHx*24=4G|%`XY##EfQ)jx?9=5KGzPzJvOJp;W&eIl>kZwLYez6r4 z4C;8q;K+Kp2$zrAnU`xxn6)}eka>ZMD#4Ab6JaaXG{R-)<{o-Qpo(Oh+~pqOn1B{5 zFA*M6^AZ1$^#=@6Mi{2a*xMjWq-*-@AHsmsF}oMyxyD0YCI4=`ZGz6@B< zC?dj%%w4IO6x(%-5z;ED_@HMZadCaSSXm`lc6{>nvk9bPkpOkZDtUD5TWRMTX~zWQ z$X0l@mX7e9tN1z1i3k+C3L9w${qu9Bi7176A#5)jhCQj>)$Uk7-oz0TrD&UGjI#l) zYGT2>*lbRE7EEmWIzgIpZFyf(&#&snTcSMEKGrN)x!$ai;@?0Z6$k6< zu?ek)FhM!V#qJDiZM|U&5eULVTBRX__nNpq-Fd*7pc&Z1!lols)fV#1Kx1m!O^@9Q z@Y*Pi2x>ZC#?5KHZK2;W^BudWN)|FynN86C3FgTbP4U z&p|&r)K_E&BiH7wgs7Sc4>JBw8=B&i{` zv4gZ*4x!HYD@?s8MDrk58-QVQ-u%NcVA6LESrJ%HKE39+6N1hziUA`H6Uj`zR3u`!MMLtR5ipKA4avFNl;$86JlO5puj5$ZqT~@DW$;7Zd>( zTQa%06Nx9k7g|E8+kAjN-MW$+&h!{6GG`4qaCS5dWIgm5*?bCvx5Au!;17&2HWV-l z-h(?s^=14t0{gPw`sHgS_M8Y(9jLTnDCMT7Nj^pJ>#uF#3MwVn@GdmiZqOTaam}AF zZyrzPWI%Ttm{2MAdFQ~S9tGUu+)d3M#U5+ zjt+N>-G*o8k{jw#8#)y6C6;;wH8=Z6FNr+v$0+gg?>q4_qI_b!LQ?W1_wKb6UcVF{ zBpt8Lw&w|k#@Y|FLQV6{6Y)=~^0Pm#umx7Go1+YWGzs=_dOBepxQ)-uPNX!2AI$ay zIrXjtPe{`o8r@)ZL_oX+)^=nmsB}ZOGX#8ku0B($yw>IEdY?cB->WQ+37MmjS4UD! z$`9gR-!f`9%Wc!{y1GSEbqw2-F8V0ka@fwFR{QK`fU`Z&YgQjFsQKN#-fcl$-J0V+++mnzDiq-X9vKkPN%~=ak z!GK!3?xyBVyWZQ6n&s^io2sHgL^<&jWmdE{q2OH{K*T$T7?9z<*nX9bc(A&Ax#5#e z!+KQybnEpUxQUJpXcXG&@&DNS3a~1dw%tVw3W$J6qeu%#cPIh^BHg7lED-5tAqXha z2ntftjUb)Up>!!I-AIRv^bEDPy7#yLz5j22an6zJ0#?57%rnnCGxNlKC#{j4A@LN( z*_F(RFay}EF(%`BzdPFwF(A5l!gC_`a@>ie`Auw6e* z`beu-t}hSm@p?nq4t=FT+f@di3b>c8GoS_@Trj%OlvYkSn&DHqW4nqIBW8S>EF^{g zf{p1~)uTdchUeL1f5h06W2GBTWK9(xO6EztKGlT{h zu$;%tn2KJ#Et({qf3@u2qG^0T{nceGz@%yOv^RhHmf#z7BOcV*P2*kA1d=e>i;=1=#K135?-jINf4PG+ zqDGH^g|pTfmk_FqX@)Ds5DJBHKcP`p$^!$K z)wmxol%x+&|M+o6;gVmJaz1R1a$B|~kCM{QgBE8p1|f1_ST1B}^=N&1ko$V%iE5Le zM|oM@K1oIQ20>+>^84GLb4()}$EbRf(rYi1=`jih+sxfuoF?UuL%(~%H z%vc&F1E#hjZ==ZRg+5vdk|0{7GASuSUA1!qoXUhFvVC?py&?y$qNV0Ab-7r$Ce)ax zJZgH|D3G=t7?4w->h>0yiWGh!4(o$$#p#7%!vOAgZ^pfn;e%+`F57^@k8*<1$00LA zrl;&<#O7vPtgg@8;zJN7X{?1+f|QK)oH356Sj;y4(0T1=yJ8R6TKH|hLN0|ft~KLF0-{L#(<;D3RtOE{C>HWvq15YOg0__Fo!KN>{kXt)tqq#6 z-0wFmA9h9G6X`-L1UOlw6W`iz(%jK+yK1_y7--;pPwy(l8Np@z>6oj_y{*w(ReAD? z(RBem;p8|>S&Yhr6l;VI&2gC0Dx(|G6;Yug4EJPjqi4nzXT#?ki#k8XCirS;wub|s4<4UnyOP5k(7VP5}C3v*A>tFuFonytc?DH)?caP% zoZ)nQE563DsE$NFdDlkm*qyF(bc?X;kF|`2I1f0I*tde&pkV>a?Yqt#_ms)KVdEe6 z3rZw&8fs!|daNm|dr>l6z@rai{q%k7=pEI<{ux34RuwJ`{X)N{Ki}QRz|_#H)0I6U zC3p*T|aW#u;TkhFkmS|ZPenwR|}F%rhh^!ZhXh*rtSnzrl!@JxN5v-I_vM( z4NbI3Jr?Hj%7#Zm~X}oVN6DN zn$ERYaVf7WsIX=j>E#|$O>Y-&6A>&1CSe0&QE1c$GRBORbdY#k2~C586l!t;z%5#E z!Y!H%cV@Sgv7F*$<<3@hF)Dlf6=J_d+4-Z|YPn=C|Lv12`Ww8se%b!Q9hoIG$w_m4 zrT$wFcO&LCI&XKerf!Ivd~YRc!y-TZ>G|?!g?-E$(6M)Dh^98Szaa z%Um#=BV@cEfS&PChqWU@O59Loc|z_?m>+7p`b||~Q!S+)S8CRGMhNa1T5fzM!`ynI-`Q|yGVeq={6d6&uoGPOrjsEzLVzO}~y?lF2 zZ{;b5Ze**Z;NSCwGy#Bc4N>5hP})tePxeXvzyfu^S6z0UX)Qmo^xD&x5DA zZB&Jy=Vh{&$t2y!#XA8#$hb0st;-qJLsLp znPZV1H10cZ6=4n#k<*l|Ux{c`Ncoef}x%VKjBxBkUjAkH!P8JvAny zm=rUn6UMm|H!jF^D5SdWDLvD;(!B?J-KodaZmREiz|jR*G#3Wm~Mcv*<4R zs=7S8HaUKwUeH%quAno2hwfHk$3yF(?4}HQydVP^IVsVrtg*G~B2D^GgO9hZeLFo) zEhOO7l*!uQQ{lM>%ncB~cgc8qCgyo&J6tqhXKO>JrC=rwBb-+&{6%cu4O{_-4E>2t z(W@s#Qnb=9XZr~d$5cfmd0Mq)@vjGXK9CZH;A6ICas@wMB(Ngu<-kKwZkNvQZeY|y zk1PNMDkS*y!W4a)l|)_CLCsWo;rAr!P33N4aH@00rZnjnc*zC2C_XDSh>3@kJrWd1 z@5MvF%!)cM=cxR)K!84DM&0Zgxv>Mnvux;fUx=uzFTMj}LUEz@q7akqN1p=FLAnpK zoSbLW`zRjvcGS7CKb1u_Z}iE}NW1k~BZFtoQZC1vT_4oxmymL2nbJXm<3%n6a9|`1 zJhc?Xdz0jxsA_tJXc6CztXEs;8`-MUL7IORui}wx!w+N&#`|=Pwc=8K2I&n?FfOFr z88!+?R4yzD38)$d$Y5L)mHZJY5X2F~eT5-osai8m=JQJ>?rctDIi0sK!Xvf9e8UPJ zQ3#lFbh1wr?_94zuCP{_Nkr(-dzIWuB!lCtbjByOo)=7)*wZt0A?D9$2D0AI+NoQ% zCer)ND$gV$hK5F_I47&kaqno}U71&&Nk--@yssukSty;(r&xCnxTEGp@epG7yps14 z+E>wD^mAvSYzZU9&4O3eccfqUxD!~SH5P!A;E6HF(DoHp$$M4M-EE$E4XrEF_FE*e zkW11yzM^ORd*nNX2^mLS2_GL!!E|Q<+}p37wH7>3+Oq`h2dz?Kwi%QbMzFtdU%<2% z?~O-S%TPV126g>J;K(O$(k5BzpI+LP4WQmwO(Ad%;-Bd(JMn{w>$;v zFGfEcI#GRr+w>br(G`B$O8IwjyYYGEC4@avTjOWH;69#u$nII89sm54Z*Cm08eJYp zbP63CN%8KqOpjBj8VAmwrP|0Ce-KauDr)k?LdEog46x-C?{wr3nv}o4bNaw$&?X`X zbAy4CtLV(%yH@+@dUox=%O|z!H(0McHhcLj$Wr~OJR(n=uY*g(*$XRvx*6)mo9D5# z`zZXJdk5&;OQty_zubJ)qssA-e!^L)g%*j9pDm$bwC{rmUDH^BBC@bAM6S!14ib0n zK7&vl!D*93$At=UhzK6n_AvS{luZkFZTDztvgnOnVevmRmw$7mWd4gFMl z*Xkz6EKPUUYkr}m?9gO!EVSon)cL7kX`^$0hze9Togv<&*>cnCA-dC4PNy*19lisj zLwv=AUyM$Xl`H{2+=V%T%7TK%ipg04OPM$^O+n%=ieUYK4W#tUaE5NGR@9Aqru?l<5rFa;1N^Ngc%`*f|l%m2J9*_XZ7u&~cCz z6>{k3@PRXKK|$^ovMrVuaTq_*OH{AqL7C~=Q)*6A`2Zs>O>1V8;2qH%#b^2j zJOq-n;^t?4@drHP(yXhI5wX=JX_1&LFlX7C^us?%#iZc9f?G=zJ(a>Q_0jghPZ6ac z;0NSwphD0YdnI_iLjFVCL1n668g$y21n@)i_Mf(XXauXg>k3_halt&*kj^osD z#?8>J%c5wsa<! z%{Z=%?!zaR4yL`_MflA(JAK~{HdW~d03i7Jp3Z|epgHvy5(b+DLU;he^EV9+w4m6$ zIdRyA!xonA%J~YEdw!q{I%os?VGHM1#Ar8Hu@5dsJ>S#I@Q93%vr2vG7c{&i-t61& z9#@NM`U!>8?8Hlg;J<=PhY^13jrDwR~dc4FSyCvxP%8 zC6rxd?^M_h%D1`jq8-)VcZHp5v6O*6=hgAk%k%GzV+@U4T;IucH6X3=LnEwrc=QV? zUG`1IrxPCor3k!J#7+mFUjijGHLx$34vAkO^8kSrREqL$f&!ltFuo&r+2kS-%`y|> zePWSItU2<~BqF6|hzAs@8hj_PuYwg&tv8x3c!^_J=1z{qB|VC6llpEbQWhre{&@xX z1ATh&jXu?CsY~4xkBE%KID61b;!~Zi%Y!jF_qg6K;^&DI{arv z!ES%M#_Dg|*49 zbZXL@5Vn`9Hfcfq32ZM(}x={K&j#@{Xv5EFEx&{E+MW z-E-;HJ2aMFQch#w>~oO+`v3gz-w)wSz6FudmL96eQ10$l$aOX{PyaL@0S>tsgZpko zCF$3iXPk=pMR{dSZ$*B?xAU?mOSx7&-~*PDxcZBOAWNZ8HGiZ(Z)*bJwaq+z;`%uG z|6hg=^A+*~Kuqp0Xuc-^F-57c#)qKVg=j?rAoDiwuk70{;xIzp z>QUGg2lmP%;(*=+9B!EWV3VVu;!B0+fRX%LTr>pCZ+X?SW9Ih-hG#V!UVu~$&h5VS z%JT*SoqbqO^F5Oo@0k9$>{D4e4ez?;^p&<8v!YpujIn0qb{$8zl`@6K+k}$TG7U=N z5A34I$uf2L{}|~#JLZVlBu&Z?Dgw{l;4fCmbFkhQ4j%^Ps0iAPqqWgk09&jk;Pd(# zwg+|60Rg%~+5z8pDK^Q3(9~78g8oCp55h<9?(9Lc57&kGA`yC5abW%1e$(iGA3+Hg z+L+K#VYUb;WeD=G+8G0B3LyQ(X`lp?dH*^mNcr)djIDDn)){e$Sdcrk^nwut+u%rT zqcESk9Pt7yGL4~1*qFAkBps0cFy5T*<6TK zMj#m`?$(85x!RBJ6tHv8f9}={#h*G z$sla^-x;>qV+hO#mfU?08XwTbl2*z`|9h-kh{stky($c2GVr~C!j~%%y>RITwtfEY zSrz&iaHb_QSl+)3b&$GERv5AL%s0F{KDy97H;`E62ahLvv>F$N5S!IXSu zRA|S$T;7e^a=&xmcESEDB?QP|4ku zm`$3!;fcU&z$ulw{NtVgwdmKA!Dr&d*d)u%*ww4#SvGP5VjNuJ2ETVwgd7|E()39e z;Hx9g?duU_}pQfno5bL%jNp|_qJu>9vZ}*N6Cob zv~-x9IEV4wy^a&!PF^EAgdI+Mv4rt(gT3Q9(Eb13^nJX;1cQSlgP*_uugKtEP?B+6 zhmoVU61eM5lS93vX%_T!ni-%rN8FDWv+=TJ*pP<^An;6yIy?g;(J#Rw2nx*?IuG8P$|0%2xC`hLxN% z8?)GoL0Ht85C;8i6%!+yXW?o5Fv|u}2ZtOzz!Cd^vFcOixV%EB*+TtYlMasw#$j+w z`XQq5+jTnAj~)-NdQf63TShgX1>{~rW={}Vy&e_fFIg+_N9z~3+q;aZe4spoxvNO%HKWk>^JReOogFK-9JC5HDM z-NH)tB7kdyUzGeqc*Dedj!HH?>Kss2Chy?)%hMSvplhvLDr z?%J1~jJ3u-5gz$+Xw~lO9)tB!KaM*U^K8}U%Fc7x(VMit$WkNrs2j(fBKhCSy0yHL zD-<1L9KZia%$h?v$oD^`Hxv_Q(Y0Xiq&`lg%AoS8vp6F~9u{5M12X!Kq?uuqqF#~5 z^5~qX%yAP$qWM0{`~A;NWpdJK5&ZiHQs}5*KMc#*$*=@~yOMl6Dw!}`D5Gogg%)%C zf#a?(`Cl3F6gTuojErcBWys^GZE)-3a+Tr4|Lg|x)q<#sb_&LID?)2ySCI=r>3MPL zV!Pf|_$7@hu%ki$3i-$}1^`-eLifgZ=LZ*c*Rm!8xrY)}FlpSA3=Ih=L!^~%7C|rE z$+#Z*j4wBIG{VKp1k#3S3mr3(MGNa5$jy&t{!EaJjW#m4dOskAH^ z&hm;R6G?0lfl9SXPq^Ke#W_`c&R75;dHh1gY*&>C?Bm@)Hknswe*t3lBMNk;s_313DQk~t0<^Y`xzgAg$ zXR?!Q|J>=h`3S8KF})!4xsCBd+KZe<43|Q^Eo=+puohLXdONs#>bcja()@xzw5H3E z6VkPupOtdZW**~(?W&W>5KCy9S((B63R74knAQ`^u zYk&OF&rv7Ipx#EelFMNtYOHj#n~^j26IR&Ufja-J=g-}iK=F{(!K!5{*>5oX$L8_> zvQ?|Cs)}nh)5~rlyXa`OoVK00aQ-!vRLNP#tV#hAxKeD@0CL#yzAL`2=(kl>EXb#eQ=JueOC$x-x>w=W@Wu#QcB#i2{`~2hE zN|6m3k&Gaan)qF$;@C?zJO6y8fA>M;(?Y|1*b{Pvw_eUPK0*`d=KBi{?|&)Y$)sIu zmjOFhQw0s+>gSC-2=&ITw2AENq0MXA90bWV19uy+`$}gHq9vsaHmoTc>X^ zn=@zKDEH<~C)lgDP7MMe3Wgkb;Fqk7pe|5lMrpAR2Ta!DWnTMe&4)XOV~m1>bXmdD zK=g>xU(v4jEcW>$QyriH(<@%6#*i8QXvX;W$Tf++4S~PaBwi-G&hzcJw1k6gr~|9tu@`PUB^2KV%>ZLAFLnjbu} z&^5*2=AgJraqxhjpIO0H*T(U_0keepT_XbwW)TZB3oC{Dcl8XIZy4B_=o!e1U&mlp zG10d%reNb{=fPmUX<%YxY(v3%@c6okjkT-*_pUF*_c>ad9Nvm@e14)WM#dnD{)&{RZCloTh!3p zK;2A1Lrd-8A_&-L&z{FUPftKVug1s9r}meBzP^FroPum&qaz_)f*|4`AmJcKhuHnp?WMdwTo&-+vew9UGsRoSL4Qom*L5Ti@9H0^iy`=obP6 z>8M-a@9*{t2lNXO85s!~?Vw)>i1r5^$3aH9$bx$AnjG3)OWaGW4^H7-4}O_jjZVoX zzl^VI)roQbGW#gi%0br-d-mr#=JCJk*)JXYwO<2}Ge`(v@Q`pILXh=H5G4EyfkCtj z6n-8osuH8l4h59XxSw^cgtG?dAV%Dqe=~0}LTJ_u%luvBE__9v#w=qs^+r9jSJxp* z7sis~gFsmDzqj`IhqN=qpU?kvIo6+&)xRcX{M*&&A3u(`l6J;m;!Zkms4#YXaVxAb zywt3QPl*7nl7V>tyfv)-*}O1Wgs4>{w!c{cN8M+N&{rHi37L-wPtiN!2xGK)Lz;nsH41sJ&m19&Doq5Z)P=RcCSnU#3De@ zD8S^qCUnwnjczx}Mr(&)&1;jCFnov;CO%5mM=s096!+(xePi5DG82CHKY(ncc_Ng{ zOzGJ?Tt3dx@m_N;yNzzef1Eq+H2Yr&Q~a1>-CH|G{WEjoXJ*C!MVTdkd*%cw6`tH5 zyEaa;KOc_h0~W&JIsyWMst<+wtM@IneNvkz-pf$TrAs^7KOEsr@imCidCp)ThY7=S zK*wWjh<9ln=aaUnTK1W#TAN{TfIyJqk^l?W3i*RG(o{oHgZMlH^e_atuL<>jTRDx> zGMo0p8B#)3eH`Ca0d=+nc)hwHQjsZSqvZ04=rjS6rX_goMr>$25=+`k7(2P@Oxv5E6qJnrr9AzCF4M1sn` zS>unuU=_PvlOomI5-iJXTD!rSxp~|zuc|XEglhYEZ3jONCVwEA=s>Ly6{zE!FEu&O zGS`qkPvqxh?ib2a4H+wshc4*V_a_frs%EPurA6w46_^Q4FEa1UWi?)EdeG@fHzVqZ z3rEwq!8qmlAO<}{kaUG(GuWln?q%6qg(5~_vh65NP!0jmQlbR9;9*4WK zs!<9tT%_ptEqv*0lqJ0>K~h%85((KX>iHb(Tk-CikF2x=%0~j8RVoRVev=#;;H6~H zEZ4~|FK=GE6RKpxDE6hc^nFpp3lp=e)b0Wyj`7~CUB#k}3ffZa%di?#gSFRWizQ=a z3vO-Z);G!Zt-QP)I3_F-w6Zfc&ka|$fkB-$u|-~C4y7+f%1v?29&TMN?^zQni-A5{ zzW2a6q2?Zjim1a9vLh233W%f7JQX z>v-K{d+7-i+2|8$Y{&%0b2yR)B$l1L?kmI};#5a)^<&yq$vl9zE3M5kx~C%B!4a5y zvo*i654FOB&kHKYvr}eej3{AnGpO7TU));hmys#&uOFf;>&a=0)|CmrEvTOM6>^HH zV8U_8$ozdX;M3c+x|~YHc_yVext(3M;Z`<|Wlw)3oUbnQgAUA4CwF84B_nr5EJ83V z#nCQX>Cqe}#BogqZI?P(hwqDX>9p^Lvqmb7ze|Kt10T_HLV}jR;`_zDP0PsjuMkvY zf>M5?1*`L)+St0>dFQ;HQyJf^C8@6MjVq|c?B2eTdtK>+o3}e(tuF*=D0_BD zRZ+|}glOH6SST&e?wXccL5(JhFw``g`;f}KOPM(<>zWqzN+k9vP>(xSti1C5xFb5} znp5jHH469U?gRG>YG&l^=*xAx@GWDc>)?*=Y%RId$>Xw^%5^~~JvU-EGg^8%+6mvi zOA6=S#(k4gcLy#BLP3V*)G0krAnxY}*C$dBz*~KJV}AxQF%CkKuGR8F{xYCf~b~hkSis z0#R)Mb0^&>GlA;Nrl539;gXy%Y0mD&ZRXu=jD{5&2eZhT$N#D8a(KCQV=V!)DpbxB}dWHvW4CdT@F{yvgbibo>0mm z4PlQL!5=Sx*ho8?<)W+WY*%q%CsCmieqY(+jiK0xWx6Eg3I< zO4lTvz2sH-YH?v>qzZf0j@Pf0NF~x^2MWp8z7XtfXktIH{Uo`$mp^#wlb)W(8)bC| zK)i;uRou=WDomCf86EmcvE)(i3_^zOm{X3=h6VPi#UwilGK^4dpOI)QbM-f_iBqEE z>=KR^=g>m*l?2cQpzUtduM>0e>@@EzLy=_i`*vjz>q-1BJ-l9hdIi*DK&J+1U`-Lx)9QQR=VmHYmydE!Z@t# z^tY*MHIw?q{;GD=x%@g~*q@>2ama^b!lspps9%p6hAe13HG)5m!`AiK92hOE<>gsN z%~W~PY@XpDxXEK^NZS}i#?Z@7sw|*!Tk0W22u~aq_885sR~*3hQE^Oc$FKA^GYr-v zMjPU&-W9(S`P5>sJ2C^mGI#6+ZPy3C`wcApgmKt1f%17_HB|4+8skv2`X9}yGi8mI zTq+4}yVgVzxxRF29y@*3cr~g8jr|B|>}0Y1qyH+$1%e_cbZ#oxk0O5NdHschz|Z`( z|8V}=-<{`W1A-CPuKnnU{+;J2j9OAA_7t|X&^9;ntAScEoTEi#3_VoWE}QuzoKXp$ zKP}|FE3^5$=h26}n{n7W@p<{KR_%%&Y-i${#2J><^C4jqLrj*IV@=_LH=ULWhR=SO z4e!cm;UD2vAK|An=PVkdt_Z_^7Uf}#RI&nL7}X>zLtD+(7|n{3mu7z#R~mz9J9GuX z23^qx3ICl7pr(Bm2+s6p(ut@QI(%1k=qOim6(N|t^A=x-XSHQ$b_3D&<{+PU&3Qa) zAA0)vxrI$RVL!fS14Sq78?W~mWPKfbq7#oX>rC(_+)SFhY%FGi6ctV7!C@0lN z`Kj#Fpz8B&hA^tzcAIgSRgd%XOoG#+Q2Bd@As=c{ee>us7kYDBmEoagYbLZ!Yio$Fve-0MG^6 zZX-{@(7pMieYRB4M_;Hex@b^5MMSdC-VrO}9Ky#5_I%ZkGfKWi3`&I@I?ys`G2*a1 zeOIeU-UP$FQDqUXuxV##YmK*WVGp{3-T(Q}hA=FlGHnwUq2(wep?biF1S(glnZH0= zsR&mZcQM=r)Og$h5r<5ALms@1-7<`^qd$|j4g!0vtpH93O$+2ZJqUA!O}T>&saNCf z(=pu;RA8S=!qZ>GgTnyl4Im$TRQj+_-So;Wm3Je-!spxlY>35;!$q%SosvblJ0E-2 z&x(H5a_dQenwKFQ_GTG+9^J10@;x33mHVYk{)r|d?Z_Hut={o=pgo0|NTtueYGp;X z`Z!i$HT%wuDEL{bZ+YJSRNr*nj9Ji?i^cU$nJa|l+%vq-XOJGiUCl|`Uh}Wf+MThC z+_-f{ufUPZBWJv8 zeI<-~4Nr_RsQmMFm1&RzdV8-?N*m|#E*NT#x$_q^gZ=$T)K>_;Q=Lmg)iv$gwSwG$ zx+GJ)4!Vv9nOkf3699sFoX+|rcj+N zHM*FF+3=iE1e=A|caDRh$;>P<(g@?FG{o_qKl!GGza(8v_zZ*2m}|45_f;8=;L&7r zg$7g?!>b>OKEhuZw<9aiPtd4CrfhPEbWH4^C>5pVq$_>dAZ4+kw`$tMO=r(^mY;Ah zFm=|l?#|tpQh6SIpijm zKdW3vB0BCq)yOOW#Api;-V-SSTV)EQ_I!hd<2~_ z<4^pQ{rG(jVtov*=WvPKRjoBs>Z;&y&pC$N<;Gq21;9@hcdA?iLn>aT9O*&5RV*e1 zn;F1mjs?vdqDpuHMUj8DuQSVmAIybwjOX`KNFG>9y&+|1< zW3HcuVQlx3pLXytx1$aoUG1=8M`+p=m)%aJcRI&Vw%nVkJiw1gg=FPnsmWC*vvvB~ z{xZ-gP_GswTD24J&bn)XA@Q$zdo z0^>=!s@KW|cnA|jf!?+jSZ$ZPKTTln4P@=T9LtdkY^jKHcXD^je}TJ`aX>pg>xR=) zFk9K3db7EmudF}Wgh;6}%=A@F+*uwD7YHwe&KQZfo<)N53G0odr~pVdbhI_PHz#)U zxt}AyVObYjpF3m*5{C>cb_<(aO3{ubj41bBEpq*=lBQsnZT%oN2_n{crLcyJ5amAf zUAg$keA#L5s_teQyO9j-l$*i=V#Y}I!0YJi4ms_IZQ9wn%aS(VFJYa|m;SPixWcKh zZhvlHF%F>vLHO)|hPcVSS9Mp@Srj?}-oJ`$p6`C|XxOzgR}T^UCrbN0bJoRo3}Ze| z6d^eI#xd_IqOw?SspOxi-dLQUNqv$vmEJ;d?{C=PF*jtU6H&OaO#MT2w)CP6 z*abFZgv*NU>`q=gE)$FN(9TVoFDk1^v%>h<<=PwqAYj88d6K0rJDZv@PI5leT>N&01>xlkqzj%v_NgdHaY)OF z10`*!Zcib>XKuu8D+6Z&OV;ev)%z{13{Jj~74ET>^C9gPrmvCF{1bd(&&C9_!n>y1 zRzI#4TWYuG$}9)AxclR!p*h-KeWizTQ^ym)$6PBh0gvb^RPaa_5LGa}w8f z+T(YAmg^Ls6egK|Sk4|v6}NV^o9ptna%DzJW15--vb#$yWLs&D7OBye8EbBcw0w431$lLun&GLu=%nFKqZQ46%Tev>rQdurPY)Mc*ntTxSh{#JG) z7-ii_cnDMbXs^nP`WDF?2+xLkh&oAA$nrvjFw_st)>Jvvf}5#8s z6Kq#Z;@bmM1(TFl7FFf3UWgWv&0~8OZ4|32>?_$>+B*5y^_m5sRg?n87@%bkJ$Rr< zpn9%x0Me%E>bPFc4A;49hEyM7(s}y}6eQE;;`Z?n#_yYyE{!q5 zvRN;>b&fZ(10x~+0bC&!sdd-&uA_DO^}glTsg##77Ht%b*E4J_y)jtnS7e>sjpNP; z5r|ybzTAvKV8xC2YM)G9bKuR`7$xC!mOV03kKv(hL;1OR`6~S0n z%Vai_Uj7Q71WV0$)*;}Pf~Wz^;ytWgVM}hW3KY-@$V^IwZIZKc7-!}z>|k%r!RUpG zJ_}8!?WRt;dPWfCE30ho`Rr~8g^>?#lCSmbD_T`nzw=jTVAyf1-FJ#@2|WH5kItPb zu`K+sldFm!e~l#Pl{KZZZ_EBLs7jAXSxq?pUVzGbp5Tn%wnm)cGf7i#d|uUYD}MQ2 z*d~^~X{VabaWhVO(XxZ~v++MA|G!cjD7ohHUUFf2k=mA%ZtU-A?xlujDI!S&jOB#y zD+}y?D&n2P@>JL%_p-?0h&PMO8e_kT}E{%%0nE`Y;^a!}hqqT;(N>YOxpo!AP| zyq&`fq>f~#?M-F{63o~Scqda81v;76TLVmt$8%L{OPZ_JWx1a_RAGiCSyqj^)TJq3 z!UN&bwlYhMfY0rrj1s7AaS|HE?S)MbC9iQz);gXS8s+E}D$h@7hy|sB>Bc-5vHk&Y@dzf3+6|zp@*3Au3|dcPRsEps0Tro*^GA)qMkG>BH<89v|PJ-)L<9 zw7-ZHjt0~8rzjPGVstEDuiMZWr{|)g-Cq{|Cq0N?<1M*ABzmkSv@0>`jgU0KUX9}( zL1nN^`-)D}7Pj#bF<*sZnqM)V-M?Ce`@j~Lm0TEFQ4pm$m>75GY8!octO-eY>OFHk zHORDYr;?&!1H7-`2EnNR*cK&Oqk!R$e3g;%MRbeWGin;|x$aWnxzAB;RS zVqx;1bdE$i18#LR6Q8`B`(_IM1N~puzWdgDl~3mslz=y%jiU`+ zq=6wRTcg@ZtMA2`d7BqfWGJ>Aou^ml94`du7;kZc$(Z`)-3rP?ACAOk(CSzZp=Qvt%^xd+CDQ^6RIbNl{NJY)YS4EPB z&j5xdG3-k{hLw!mTWqoW_h>iYSRrgAft^R1&?mg@IsU^+!AuL>Ib4%c4H>jyH2l}9 zZVY)bUm-V*&HJrGyOK4Gp<+Xw)@GJ8hpKU6bOrHxfpI4LML5<&Mz6tW{;?IADX;ye z-Tf*fdxrDMYOQTpZO^mWj&x`~XIVMSHaYVv#8mo4sglTx7Yb)2Uw)3I%C@zU6SW}| zR<y0Kit`k6a%lPS6+tX)Wu{lM-YD_zNQ@_}9cwVm2V zMSr!OqNAj4Yf5Dyd?6U=6S^fjN28GaqB@de@r5J9$HrW>`}8kcU2j=p_`G&0EExdv zs>5MasQ^84sK!KL>C%K!WJcPJZj-gf3vXG2=vWg{~Os#92F!#JOUu%lyLh89o zK54e))$Z_E4NSWN{A`IaFL6FR4>3|D34AK?`$-EEN^^=2g!`r=+s7HDX>8BLp=td5g5#5wdUv|l-~3G>veT~G#ZZ`* zVa{R6_gUeM(Q;IrL14eq^t*N4KcLe8na^A*e%4$Ra?40JRFe6f3|nYepmrtMQ{y$Nv>Eopwd(f{RDb0)l}LDoL{(|u zR5&eR$NHrTdfy1~2-P7kBQecpGV4)H>dk&N3F-&I0GnXlls2AaQW@W=Ex#lZ<2b%C z13iYGR&^2QFXRGMxAK6n(-jb!)9ufs^x*wbjDn+bIaNkMf$M;~%;Y5t*v#l2X~K9( zJvs9iFF>uk3NrH#{{TL!gO`3iK`W@=1WSg-rIeoTH}ebHCgTh8QZMq03W6Uftf8>G zuZP)KKL5zQfmcxNBG{`Ik;SC>OlOH`DNr3E32ZdVMf!v**8>%9SFdO0oC}u*eDi68 zeeUexsydG*RRkNNwJn#oO%f}gN-%JL`q-cNiZ2k)-TV-8vP}Dm-0~ z$dG{69 zG$m0y-RCHH#e&|Us7DY8boX89SOC$%olfvaeq@^nw6oz`21W3JgBya9b(U<#r=uyN zf)9JK&kl1&CpXh$S!&B!SY$eQ%tw^!P1_v_&@ zJ$^)Mty~^bY5aF^tWD>gh|3Yr*Sg&KLRu1-s>Rb1 z)$YPJtTJNt)U=rlrO#dWQyf`k@P)gwcWFyZNw%?l>_AJFyM4#H>xJhn#7IMT(&Ya` z+gry)*?oPZgQQ3Z(p?HVgmi}@EiDY)QX?SUQVKehfHcx6G1Sl@Eg;=U4oFH2JpP{Nyze>Z^FHSfN3P*wUwiLst+m%$-xbe^x6@oNqa@=@hz}x8Qv>Ac`J1mZ zL>#b|J-5eUHiSBhlx>F$6R0$czt;Kmk5*$eMO@$@yCP7 z|2{4l?Xk)^w10uh;&gW$^u~I%!$`GF`l4!E<#paEggsON*41X#E%g-XL4w=La8Ift{%}LzEW`XrJn4F*HypyKd+g~7w_qV$qxJfJc z^2EMDoHN~5Q)cI2*caZB@ZfQ=ic@x5aZ?8w{+^!+v#P~kWI$mtzH*^;LtByfAFfaf z+j96PTpU=y@1+2w4p3JIK$zJ$xlvjw1O}cIpf_Va-h_d~d}bfPL!PF#`!09N>yX9t z2tVltwsGzY9jp`WFh$~rg#T>nzYPV9Bn0}Ptu~vb3UaV9Z(Ds;+lc?(O&U5ZrB=2m z`j)*;|3P{)BzqS7I*ZCpLt^{}AT$6%|A(ME3K-HrkL75sLv|dn!GXQRCl#7~M&&(k zQViS#{H8{5RXFC&Q`xDw1&3H9=(zG|3$YYqt%sYh!?;g_b6L@Nf#at6o2JN%pmC=#~w>F07Q=A zgPfm2If>za5kf>%t&^^HgJPvEA*opTHQv0xx;R@S#}_;jdu&D6?;Q*t*HbhImR4sQ z{F@!8z2Dv9O}MY~Y;V^@Gx%Pb`eSX5vNi9=4p`elI`vCJDMFur3lgCeV95jRfuYw9 zaznm9qlMFp5j5p%cb{drDwE3LkR^&U<))2yX6@d6CbRKr%NH~FE+rnw^A<#2(?6SZ zCM%;24IAddt|BTEh@$a>84Zt?Z`Mx4gX5nDTWr)i{6+DKqy>Kq6bg$A;066jN(wYG z9{>mDKcCs4_jm>?eiQokCaZV#Wr8Q^fSfi9R8Jf$1B7|C{1&8oiX#cN(5*?EkMFL` zB=EB0<_TpMlD?n2wvpqldtIaEjb#(_rGAdDeVHGy7UMq&taF_TMGEnC|0P)3z}#uv z1vhoWzEs@#EU0-ehb3O$HS_f-e$ki)%Z2VsV7C9!TZ%>q^cS=K7a#Qe@^ z%Q_;Uz=|y}^?^eW)DNHDxU(=7nTQH3;}KM)g~Vf(yI?6tX0td@xR0i7@$r@;5pnr( zB74$3AWO7)cS)(DMS5fk`o)a8nd>UC^!FKO=QSkH9J1NeT0cy+PC)fDKQll}SOp3% zwRbR_t$4l&k49S6FYA(>c<5-FNW>^rm%gejgHg-4jPNK~aFey=! zmMPl?Bm^g0?s+^A!ooOm!zxRTAsNvN^aUlJ0QePuXCPz9u@R5i( zZ4XonOWlj$O{|=tMj0Q~j_llpP(=}mA}+}FqAwD`|7jpe=_eU;TC)p@W7d3=m4@cX zQX}&%`(@{`ii$$>Obb`dL-m!a$-)U6u?{i3U}?htX3l^)yZxe?Y?Eeg2OBV*q*pg; zMe$a6Da6!_rLDFvu5=rGxAzM$J3R3RXq-;&kiXJds#s-x}? zg8U(dtNmK`UfEw7zwN5Q7r!axFWh7#`OZQ#it~zbCTF@FyNH~RNP5??K{I0hMP*q% z{@b}2Tn{FencVRlu6+9N%ThUd4+uU}9&&C}g|c#XA?Cq;7}EG1AKi$aHp5!Te6Yj? z_*M^HOOrHg;fa9Y$ap0Zb+5G4IAYwANNy7b+J}O>A@Q>gf`Fq+yT|Pe!!;#zttc62 z%L2P50eklda4#{WJrUPw(ZYE}Jmu_ZDmCcgc3u$=5PC$0{SQq!M@H`+$h%UW@I>ac z^9~kUfbbq9rTo;iFZXGF&K?J`zd>KmZ#1ww^(@W|ir0>0)YVTKA69Qx_XAS=u6)%o z+i;OXC@#w6GKG(`B%9c!8sJ5L_~z5`EA^lvM{}#`b2~9z)0vOVS0|d6TaZk2o6wZh zBPe5uz9&0}7JjbcE*rk?dqsySqfV;OVX=kpA1tQQ{#z!{efUrMEr-dO%jgp!ETDA!u`wI=;T^RoFwDSJ$Ta?*$O4~q$3cum@9Q>Bu8K7+r) zTde_QR%*{{k%A*F0M$i`UWF!=qp6;lllIan?2e{^`Wl(|YF~dn3fEf?hProRIAS4@ z0|}9@S4(X#c9y{6Mk;rl$N4i6oj!sZ@#?l4dt#AZid@)Z#brZw5P;?Rz{KkTd6P3> zrgCKD=jXL`c;v6lxUgGu8_ET{>(4IU)=pcKC5$4Q7~Qq}8UN;Y3ak=^mr0r6_Nnr- z?J?&JPh@ToIW$eL+kAfXFCGW&dEU&dwPMre$o z^d2-3?IG*yi9uzPU|{P~dPv{e%alE#(__0=Vw;qRoS`7;+_vm=4tB9@Vk54?FW=a0 zPiV7=T3yT#W4XlQgG?}RQp`d3d`)Xlq_5ZdRra*n@Dd9uw2zNpakkI#l!i5C>2Azj z!cSs1g2m|5e}Txi-Z=Ap8w!&Z0hf=S4w^d7syTN_&qz&u`(|*!V-v1NRKY)F@cC_vKgx)HwcbXmX#*(Cc`A|kHnI)ynET@)Za zp`Mww*|hRSVx0EN*P&c=JIE2IHE%k>7xAosl(-?1T{ zqgoDk!+Y($3V%44!6WcRc``TCyt|>;xIesKRy_1&3znwiB=4@~Q=~w8l~|AJ%f4|@ z*oY(*w-7J&8yc;hxaaoL3ndrhc+i`mR31sdw$#`mJe@iYR%mFDq=-s8mc=y|$wA)a z&8Ib8gyy2wlY3{4=AL{l@AxM7MRG| z1@?Rs7*1+0f=ZAg>n&=`CORNN4-gx#H*~vQKydU~a*8CkbUm|}3qHV-?C(lnmqwx`)7L`K)zO)lFL=haaY_hv)`~ zHV&m5^M4sp1$ClaUMfjv(`YcBwSS=ag?>s)C&uS;sEm>=$6LFzLYKB_tDV7OWpF$bL~kT@ihGEOcdRh2^Xc6Qq?Mv; zh+L$oIr-g~)g%IJ7Ad;vu`5^H)3WH7IPP$T(;^?cjx#NXotd$l9IlyAzTQ_9F$8Vj ziujUlyWwIBoygLtecIuBG3KNvHsp!kHd9|AH-6@}>@U34#xdEOT+ou*MTrc)kB;mS zX%gaK$8W&wxn&(z@3(Kx>M@hpLzp-w>GroH73szAwNgsm&*!#O{}hiWC`YrzfGVRa zO*!BvO6`m_+Qq`LVF)vWu8|T3*=E%nT)~RO7)y%}ZOgCo3fRQIMs@%dd;nFvo5F)u z3QNJ#r&&kBE9uf0T8Vs5c_oj$G`%NX_k$zD(`ci8_4#7ZNaZIPio@gGWZV58Nl(`| zTm6`?*y&`3vYwl)(e_{qUP&V_)eRpHcZ$a<2#*bXV|Bn6qlI&!>>{!rwNUFvJ+@j&pP6(fI~iRVo8z53~-?k9u^QOG7~y0twC)kbki@Q|?VIsF7sEacQD>+2gEDI43y zAae8oBkOCgh9r}#u5pB?Vdip|3rCO-ALRyy41t*Pe8;Two?H_P@P5|TcWWBl}fhgRd+i4el96Th}5 z<|e&6ORr-OKQ0&Z`~0#IGV>ri2LbgA1}!}Ew=I?~`QLfI#B0x2^0eK3I*~gBGUx|f zAhD~htXJ}%5MS48Evp~F71BHXo+b?u8W+2XIm(O0(k*usm$!^Qv{0^C>kK-=jomiW ztJtpImPo0yy~nc5;|G7EY)WQuaw`oq-rQ1O6;jan5{6&?(tDX~iM~k9UcQ%|4ln&W=-HRo@npmH*f(a7PsdHz{+ZCyocTA$+uU`-3}i!A zyD~%*`(<-ZIR@+4q2Tw27Ez@_Zj`}|XTKALp*+3Ps>4xRo=pAX1EqR;V7l8+k zlc>anqzh~{tPachhV~l{b#x!br7!UlV+j|xZuDJoH^a}*jJ8{(k-MfwES#1Mn`o#) z3poFF&=^%?NegK3)2&jKmVDxmxxtcQKW~wwe5sH)?oBX;s#&q&s`R>=dT@=+pTBU^ zb4@$?2*D_>{{s01E0uokwb#3>9J26EQbU%~6SvX7x2eNX_vP9pV zZpJ@W#oOGLqvyX;Z4fB-e|bAo6XCSeCK5h9ApYa?`}!rO#1a6BE=)Kx2mcsscNRP_ zGeoMhIgy{{^z-w7$tE>A#1ziA#t-k-#$`Zc%Y4R71@(Gu1LMS zIXz2~KZ)hR3Fc8Zx+%#7L}y1S(q0 z@up48b+bPQ6z?66R*i3Zb%st0{C!`gAAI+%@A6L|8vm@RJKo9_MTc`2yPP7gxDa0! z8APU%-AyO7oS=UtTPjm!Q`5A^;ZXraDqZ=*rx5|S03-JGg$`{=8I)>Xy>w78cqJfj zx8Q{M(N?uVZ$F{hdx1%;q3vHFvP}Qlm>pII zr>WAW+Lq7@*1I*~Mjg{FcdY%LzR&lvCe#oncVd{c9!1BiwDd>=)xknTreXKrZeD34 zX@$UnV|^`|;6dKgqNXYR)AxAzVsL2l#GXs;^QI`VKEIh>G5bHS?ikk=es$yCuca}+_6Z`81`_lKFX~$($}?Uj}vIalX$UVb!^5} z7c!IGfjw?XNf+O#zQnuiPe;51f*@*6 zN|?p&`oJX)>tOTyg)`I~wf@!aa&)qPA7K#7Y#-P+!+QM%oW4&J1&4+2M}?ryIaM>1 zpTBKOghwMS&+Ickn-_1x@cil?6y}u7J5<&t3*fT66x1i0tvyb$__RI~57pH~lHLwX zWk~6=rXZMYKqm-j>=I$UXTaXo7ixc-W#43Qz)sfdKfRH#N-Q$nPqjfA_hmgT2N{bu zH)3O3{pb$MFfO*lAhei|jr^OKhjfn?it5w6a7cr~g6nt#ACBY1><*8$AbIL%e1zwc zq%7+0NO3lygiB0LV^usfo85B@%%=GcazpW^GhtXqMNnrG|I|UJ`1Pz5>=b)dn)An! zR=w#;;0|c`a5Jj>)b?oKl_Da$bm{)|vV0YJZ~D)fRC9xm%-zKltJv$1GH$AiiVua` zIFWV1QqF;nDhSJ0x{QP9Nmdt$XuzFaYmaTBCuyGcDdHdGCp@zEoOCna}A9FP^5G45v(qAt>AwM|;SDZM_ups=sep&r)_K zRBX%sh6{;L?}1U<5pmnpDdqT%Uu$MEt*8KQfDO^aUhN={G~*sC!Dm<4v6EZZvl-w_ z;mL8mEjIE(3wI84SEELv2V{c}x)@hLJy^}xzS}^kd9UdkDZ%#pIY88bmGL>I&EM8K z-2l_-1vC_4hb#q45OeC+O*u`izBGtYFz*8@y{_C!4m0%8d#8Hx(ecn=yZWidOW|ZHw)RBr|=)MR2rFv9PYrHG$NAfs*Pf3arQTI~@$cFW3sCvK8L`$GPB0$%r ztW&C!{MD}hl~SolWD)&q_4PBjbnECrZ7dmg62JJ{dgm0l`Vrft(1~%wk;oA-$fqr* zyx;#5&+a{14Oi3UaH;1ac>;zll5%UpgS&MiqdxIsR#60zZs75gkIb5N2$+E-;ksuw zu@1kAf`ZqEd%fq;6T({wW&Ywc|`G8dt#nbChMu!SDi?tu*NNyx+VA z#-f+_hE#ix3}>=_T(-~I>lb<>#OPrsY4)1i`#P>^qbN-3bLj2(C6&dNrc-1Os2BUofX3w}!D;%;~POX6%fw_W?U&=TRqc&=%y{yI;>%J_w z%HhA2@+3)@O|-%3ei>K_RA)Dxxw<6G>SQ9N=Hm0jU0}{&I-OBpRlld4st-71I)Dm8 zvPd;k+c}NHddBIx(tb}T-k@7h?U7q#cSPZir^Es%mmln@K}~{kk$UD@ANROzGKCig z#3-#oBiTNH0;?}3sVrs zRbZ`b{^;bCX}gstJYc`{uwhCVf-ng3Dx&|i9OKec5%Z;pWI^A&D^S*{C7C=nwGl6b zG4b@Y?jzG>5h1?`Zw`t!yV+;Yiu_3{T{f!BFSDe$pjNB#HG>UAA=^iycUj|+FUVK9 z|6WMOEIQdU(dwoE+ag-K* zF*?KgQNsm<^so=}SnwKJj{-X%p6u-)y07>F03E2gUkP3uvr}TAFYJUX3EdRI6YC?2fVn zs-?j)XykKx25i`m6F!3N>5BlX9Wss-pb-97CUBV1*mJ{|345|UwgED~pWJ@%tWD^! z(|{GQwJA=uwXVcBer#6wW|OLAJ_(^y(U@dZcWwF^%Ub#2?%O*|m?>zVyLsUI4m)}o zOaow;p{1WXxt4S_)GIiGihhA^@$O;*lj>Wf*OWc=kM^r5KE82W@bP|JA5}Z52=h-n zCMD~DT9)T+SKmmFSj;9hnD)djbbx!)EQj>?oFU--rDo+_h2uqHaMSTnM>-)jH=rgj z%S?t(XXo^U0Zh`TGe-)BMK42x%AX?Of_eo?YVX8m}DNd|ahkFFy zNh*%XRjO)wlg|> z_0ii3In7D|6O5A^DkWB((Fs<8vN(%xL~Cr#8c26WB{G5sYHtljW1C)rtBwY(DYw&p zgqoAKgrSp#2sUeD8Os;q%je&AdX!y`yd&>(+jb$a?^sD&_bA1iM7XeOP}B?WoMn=a z=O7I7>-F8l9r3K~&7@*5D++$O+=pNwNmXmht^AAd8isEyS!F6Qo8Fq6A9gZAyl`El z{CZQ&2s8;q7{7gypU=D7ul*hzTZ76U-?i=Z@#1|+>}9Y}5C|P;QKvrhhUF$?+wLlp z?F@#~GV-UbNj+LK6pCD~>AM|dv{SZ;uNtG}el!#96JpyswV!WNos(^UMoje!bg8SU zmSOGfIDk@mNB>xXD}6S@D)$h#_nP*OtXHEZPAd4a+u^qY$AzR5hCsD_og3M4^u!0yvAr$ps4B{)=zq@)X zUQmhYy0`UC&xKxs!;y)Q{P4G*rHO}3sdj-vp&Kk{DcfKP{~>im&@Gjh{gsKs;Mha; z1rdm#x}=ldz2*b5Z}JY=gA4>@{G*T$TTKB?nYm75usl&Atm&mI<#@>=H6QujB2eaO z(dq$x-zO7aQB^k10>?SDl4|G_Yy6@qYLM<2PO+V*G^4#2{^ci;Rj_neC>BH5IBaCp zS$#VBPC`6|{u_Q=z+Fg*pii)~Kj!M!&VcX~qE}4H_S`~n>|k4`%jbA%fJ>Tryexkb zk%Z8B@?z7I_2ZLea=SYMUCy*vpJfp9TN1D53qnnXn3IeV^3YYM2TKG~ds82k?xiEa z=o59;X_hxyz7SU;80=l!-|n8I&HuY;BWcFaeOHrdv>b;+Dau^BTp>m#X0my}53 zo=V|nkFAfnSk|a>s9@m2hOJbzaSSs2sQgR|c<;;^cgUA^phex?6|Nzs%;7D}&s~a2 z&7W|@_4;Px*<-{)FUFM`Zivt1UQE>TQ4f|nlK$;&dG0gOS@o^&Y)?D@@n`@xfPSUs zs9}6y8!V08-PE<^W#)J9I3`iI6T_Zh8dV$jqoY`c6{xt!5D4a5ZNCTyDi8(6v`zIw znS|2SC#T0O7jE`KYcW5^TVi{%j`%q*CvP%E28lNvPEL@8_GdtF4|g&034zf11dIap zFOXY9R)p*kBhXM}M6)pyY~x^OQ7kz_S&qg?f-JGxe3ow!mA z?|j;8U=3KekZz+=`+0{(h2=igBStlo_+KDh$BwzruEw!iA1eSihH@3-bfvRt4Pm7l2||6|3p z^gqXr`)$RTOuu3&_{WNwB51p-F3fDHqjm@$X- zZ=F3Sy^|PW5_DTT^nckfFG8qCpG^_~IH$$`E~e@K=y(4oIkrRatLTVr)4T3apoyC0 zgDuh!M=ND|fpse|A83 z{$Av|8u@PTq(xu%v?P1ADY4@- zmbQ&w3Fh~>;FR2`0gFP; zlQATZ7ZQ1}`G!6xn%QoiJ7v;Ta!$vK*t$ON6^toOM-dMsSX%5LT}bw!Q`Z z)0BWMpVEu{E>nV=rGWbgVJGxqxUeNxsz_g?VWj~3~I*~By&3W2D7;ST7<2k$oCaIr6v z%(N6(6FxtjM|1Jp(R#7i#!4=E(J(A$gn6I&5q88OM$f;a{Rm$b)P;Txv#aND;}0_u zJ(nSn-PaH5COrkRlC+JeH<^HIE&RKMKs%1rpY^r41y=Ib2u~UOsye8=k8;v#j2csJ zO~9QzW7DnUW)=Y%MaQHtcfRl|#WQN&C*-2PE6(Spl`+cS|H95PQGBEI=igv5$GA|5 zusN9~g?kDC=LY4j_b(9YyXa+M%W_9>7Uf8+t{gq{VHohfM)ftn&PbLx)H6{`TQmx! zzRy&F8&zNvXY=~l6w(DpqZ=;nc6j0RQ!<9Xva&XYk?-et;FK1axzC9s4N+cO@q}AW zc*-G@cHANyh(|IE#^KKjpnOe}^ z9nMbj*fU}`2`a>OvXf6`=nPRe2wX8HyRMad-DSXpJHd0n%e$WHHpilc`wN6))D>6qgJC!)vQ=~rp1&Sp9{tGlIX_IzRDp?HYnYK3&c(^BbXG~8RJX`i+ zJ{^$b+7e;AR1wqTzDRiq@ws4;o3{<#0 zjx6VWHlmu_vfC-qy0=svHzX-R_Ct+KCs{Ua4<1wt#su7PN=nAfr5GKkp@OpZ)q zw63bn2Wz4YIa^ zbp>N%`A4W>-PoRyVP}mO9^B}0-B}eI5h%B15c#xXzD%3k%TEhe<;t{t(M0*o^7WIe zoM#z?Z-N5qu?fcbV5xdtttFn0?Y36u&$yLQ zBq{K_lR@jw%m*{&b3uI{f>g0mFap3iDtQh5fCkdlm|adQ;Jrad_(273StLPgdzf0R zdej}vquzwo<*TrgS8PFF#KsP{>@>1T(k{-3Wflt?*d3#e&Fnt;jOaACx%@!k@Flh4!v2r|K~WXvPF)Sb`xe=XcQcuq$B;2#+UZkg@=}X5g%ezk@;-qw66{~Hx5kUA+stuW!4qo zchd=%vONPwFAJJR@yq)FLPxC?J}9itIVsT#S0vVa{=mfWG3s`gi(?*A?L+ODt+xe< z7+wfd& zE6Pv40PYix*#M{N+b`a!TQ#jK@Gf$AN$YGCEw>k?9iD3x(6jToK?kmYJkXFCTPh<8FpbV+@{TboI<%9myiew;* z20!_Fegi}Ab`pnx_vPE|YZ%#bBIUi`A$lyVn7x;DSy$29m{ULeH?r$EvWsxpNnnO9 znm>zaw?~|LPhtTlE%{8s?knc@G5FOM<*f4n;Od#mZ;jNPpz>`)#yD0aAQ=Ts-uDsc zKL@-SzBwr8W|zvRKR!uPvNi4TA&JXM83c34nQk!~Z3(*&&+ZIR*@s$R<~GPuqcvuf z>h2gd()>^d`MnS*=mvAYAgmX>4Q#adJ0EZH ze2brf8mwEEFMYRn-^t_lqzq77OwZ}1w4OWTYshczkvK@`HAsh@P z+Gh2|J7*WB*^L=i0%FA{AFVZJv@>xP!Pl!pAYYizpi$^5@nGGkuMU5lX<_o(H*lXa zqpJAYz1maFo7)$)Asexl%!`zpOT33C5=2$~j@Q?KXVZ`fe$!JN*Bxjo0i5v*SqBi` zmCJX$9@69F%0{cgKm+%L-)%wy^nbL`voJh|a;|>Lj-Ivlg8Fys7jAaFfD{{jf5l+h zL%i@_`I|W>&~XHU!U%u9Qi)b&k2}UT#mKxkJB5p&6a2zgdB(z+iYRU}iULixZ)d5l z2AE-?9)yh|ykFtdjO%Wja7MPT+x|sVO?0ov*KSSN7hfp=6!=aiykIP7otFACHy6cx z70PizhvT~$0+666=jGJS!E{UB=U<>BI$)-!-n)Xm^kG<2lHa;d)Qy6_*=UG^JN0Vn;JQuwO*a>O^X2z_M{}xcmdY7f z+ue3a(Y|}LPq^IG+QHwczW;HIM2H-dcTRwuzlUXU80!cQ;r>aq<2S>IuOY!_72djs zBxy<2gY=ZH2_vc-QuIP~e#b_ocyzi zbHbQ(g{KcKq9K;8aBT_O7R7NrN;_u0(YNUnqiCIwaJ4((Z0K_0 zYsB9L#1*vBCtULN5XFDYuJ^->HJ5VTRvusy(O)Nf9LM3fMY--HZtr0!dSaWB{?$9wyD+Lw-sq z<^tc5t{}ieXmnzZlvTwUxrc&ZMX&>L53Tg_fKa6a^nEk;HO+I9K0xe={Ps@>{{nf1 zWI6=`?vGFaelV+Y0?snPQULm~{O5NsNauh-yn^kLefGSe58;=3Td$aP(u7zp2#*iw z0S({>sSmIljqLmnF1 zPXy}=K!U}xNreyDQcgGICtXe9-JCXu=ku;6N}+d|zx~*|QW9Q9&remHP!S5q0b%D? z0+y7=r`f%P)_q%2yqd;V1-7i_i7eOCWX z!Gia6p?%vb1=jZ^wqKyQ3fZ5flP>^koO-;ax!e3m`1j@|2FuQ&0=ek#<;OjVld?mH z-**(>F!o70`CuyIoN}m8vo_I9&pNce*P^C1Ur)4DY+}wusFA@l&-L z`vzVT8OP?5Eib=}d-pB%bQcKa#jcr=3@SI2{9X_E@R+tnuR{*G4=Y*DD(-Z*-K?58 zIm?{;@|+O}xFD|h?lvk(4)e*rDFeKmzNSE^`DegjHZQQb0<1!o_hEADs@XvHcX9KTMV>) zz**COs$UI;|M&Bw<#1OEPHU^7j|nBnMrxM+A{u;9(5g;4jdfH+q*{ zaR2}h5FPp3w$KJj+6WfdYmrA1SQxC5uwzHDmdLK&LvdaM{ZHD6{it8> zzkr{lg!a*TGDNu>XW52f0uzq1Ny#VSp3z!Z-VCuZS*&oiLzYyJhnzZPl63W@P(Q`@ zat$$3nV$(;i_9Jcy|`DW9+x?red`$^AP2(HaTe;e-ejbyz8v~R!KYZRxu8wnk5GtN zt?|%3AWWQ(a+8T{()cNWE~V^&7;NA7U%3W&5?e_s`(icTXvLPRq4Immg-5ak9S-1o zF`o>xX?*W2w+NjkAjs#F6U!!nc4Ii}$5!me^!8~ieYQ>L&_&&v$A#Cem4IT}K7M6p zom!oa8uD9w6J1NCohuh565}ezq)U}mj?UDNUuoK-Jr9476#e4t)xDUTB!={Ot!YQf z+&{htaG2jegELe%kfUpcI9VEjW}GPMt4MdC!3(5|`J7M>Uy>K3hp#k_+ z5m30)QS=y@JU})y!HMgZNyC1Dp6(|8Ck|;Bj<5~?I`u1!P9Ux@_W~#`7v;813 z6)&Xh+O%t_56s$$BBk!L-ToXh9&Bs(?!8PLmt|HG;C*jDy8R&tVd@=KF+QOb_!Y}C z3c`N4wcq9iq?{~6B@lIS&c`NBVq3(%i8E2c>CGif-2$DO1C%P|r0khguEwX5I?_G*$`tT+~KTwrH-{Fa~FXt=0?PhAzB*sgQgWFXi0} z{?g*>^X54<}jO7 zVYGdnnsgL$6EHYra)AwLZY5y;QT!^jLGw4fNPRg;u-hWyn*G-f4VCp}tvzPg{F%6I zMoV8S%v56Lp*d?yf2GZ=HdpU3K%f94OZ;_sApwWc_cx z&-slDYEMG2P;_5k?XQ!_dsbXj2G}{Al@m`Xv8T!DR=4+s$(IHUAclNi&Fp#6z1|lv zV4%yhEb(PxZm5ZCdllL&d(&YxKxCF#C^RJX)r|)JyI-Fir3%M^oFKhQQhSb}^nGK( z{@tAPAwWu!Vfx3%rjMHI?6DiBW(LPhrKu@5Gl5CJUkB31b9W;oY})0`JpYCMc{B0; zR`j#>XE+bnekY-FfW{rNp0FzG)u=j}GeB_oyRXNEe0T$odD zym_ME;yGwphzOOwXlK?Q-1V|rfax2tM>|RU21NsT@0V=X28dnB(st9cd&VhiAg|BG zwI7Z84Bzy8uv99h$tD#SOYXJ#;(qK6H`F{OTPp;AIrYGdp!`}|?mHuPsoV;BGBnAE zM9#8l&5gA#o_^|L$7|W`ctCBEsw6kH?34DLN664{7CWp1E_29!-qaHBmj>GXcgStJ zzMEiPo%#C@F#T#vNmbsLMIh&J%Y5kq5{ItAD02MPAzUkS1{sJvh1ZtuHN*swb47(A z8hb#oPyPkF#R9M!L-MX^OV$xr`(}^eP?_sPXA`+GpX!+Sy92|O*ma?;9d)dwDp zwb4dgoK8axbU%42+xzpg$ksawk^P59tTdgYf^dop(0M+(&{7LKTg>_B z&F)E=0EEOQ0UuI8BbcH5pTf~9Jx09UB6VZ)a+RGABLOV%b;x9hN^4Mytb~ndpux`l z=9$Z`<2*m0V!QHL9m zLP0bsLp^qHY{e$E>yGiaKbC|Qi40^jHTxoe$B8YPPdRBsKE8jJ`gt!D`uCFmS_aT; zg%Ir(NM|^2N5t_0R*#Iv5*}m=yY=w0+&KNu!QQMo30$hyQ2lf}hqZgBEf@Z|I?;Ev z-^z70Mb4^UaFz^-cG1y{ako@IifcoaxBjs{PX+henM=yCN;>y1{~Wt#%Z##h+%vhs zQ0aZ2q`N6X^v@YGD*S)BG&%U~?7`xkZX*8u3{Lq1*AC*+D>7D|cex?OH=KkZHt=M-*Ma31&1 zd}haW#oNOp`Lu4r+NMtZQ{TOc32*q%h)u;)%;DAcZ1TOu*?*@a0#Ay?KR4jtuiP>p z9)9m_xbx*D5GYgf$NMG0`zDW4&%Z?^3M2Dj7M3u@@1C(x@bn^ja@FPScp&;8&)mWm zR2bu_eS4#`U7VT!XIc9lwE3m>o1Ri%Z}vQh=3iv>*WGdq9DaN-5pF&oB7=_eW?h>@ z?%bVQDfvu}btXVxnC3rUM+8!u&I$4qwj@fpJ2{m_x;^fYbPFfCF~V=+&pEpENZ?#_ z#|iOL+ZiD|^MP0bl1v)kbl0@`Kkg#pBeb8epT0+%!awX-6e-1bT~iJ9B3rmWytDJ? zG9(`(nrbSQotOQyd!ZWXe@vXHF>26-!@86gJOb&MAIi+TyBOVB%0FUsV*1C5;f28=jHUh4!k-S4&`Q~&#?(@CFu z981&__Y(7c+`hUwPJhgs+m0H7GL0`n<=#tV=={Z$$q# zoII$jY;osY(~4F#0l>7j*N(Wg8eI5qbE7~93J*$`zDa$xtAjRIZ=K+N)4I@RVU{ty z@tZHV{<;|?0dxN2N_)y9(o`Rtalmf$0>akDfWmzpljb>UAb1oywe;6Z_(#CwVQ6QB zX~~4u$`7%>5SxZ47aBWwb<*3+w_fl3uZRElhy9nUmh@ipBj(Ij^S`8==XWN7zO$=Y zamqxO*9U-E`u6%}3o)Q>PhLPuW|Dm9G4drRaNXSGPQZLZUWNX*8faS)-M-`hBkd&N zzV`KTKdeKZ1y!|$$`hjD8H3IBu7uBHvLxW>xs`yj~2$U1v}IiI=N zdHD=!uEi2d&ry3pFMHOJP0fK1I>&x~`%HGNU@ZHPwGl5^D)uQVGcULA%r4efL&i~U z&Qq2=Z*jP+Zbtyu&m)Z~8JrwHrN&f5X(dUooPEUpmY}^EhVMR%VRZG{t&L)E15c=Q z(UrIq8P4-V?H}{+C>UldrC+I3J{+X%R(11>(+ZHNf<`~uOd-dA3>|lVu?MGinzVX! zpyKo#YRm6*JIY^6GD(8L4??}xPC8gzH19OSn}KYWkO(DZ8D;-c3#Zwk<4~OJYj&Y= zYy^sI#*+O4++*~_XS9vIu7J!wI@+q zB4yo2J6m@rg2#be*mwFw1WdOztZ3O9NJ-I!2aoY^BVP^^2>;M1WlMBTlvdD|p!zV< zGGDRZ*n`b4oX&}ojYzU+NT$8f;3Q}=8Neg?tu|1S?u;f+`8J0d0^hOJHQ!-I$2Z|y zY2yJ5bX<>GDk+Jr?f{Wo5hKz|(0*t1qOEP*CdaT-10g4orAHhh0~Hbsdg+lq&J)i& z@ikR%Y_cA1><-l&9X>J>FG8<%#TLt-rR#ky?XSq4@0{+}oMR)q1}9AhxStU}o0Fn7 zSwVo@Bs3#E8Sz9AHp{hZ8+)2eIG` ztAEt7PLjASLqE1QF0>xSMeZ$}<6!MzomKsWeZ+(?BV&{Wu%(0b)OC7<>F=`yA!$ng zFZSL7F3N6O7#~1FBt${Fks6WiMrrAklx~pjumDFwK|ty5A%vk3q`ON}hHeJw^8bwb zp7WmfoO}QG-0!{jyWhF;`wjCv&+KRKz4l(U_u6YMH&x8nhHIxOFPy><7<-amA`Vi; z7>rMR{a*BO#_!iAc(K#(ZL13T5NJ01*4{mlreWn#5slod%eaftQ7X0vhIgAp`8sLB z@ZY-UZ9(VDlwvyw{6Trl1bxTTNX%G(nVj=8w44+4`)ub_1~2g`KN%kwW-D~CZ(~a* zxL`*8v~B~03L-UPfZ5eEYDq>{)D_E)-#r)fq$~6mTqHY;?x5M{l~KC+;C*+eePMfE zD~;e;ueOp|lZ%1e3w=sXgPrCN0aHv}vSKl7U^Cki?`ziysOPiw*DAE1!^&x|?x$J| z9<_Qqwa^93QFp0`zVHdKg|;PFcuWWl`)0$E!@vd7AA`PsEfb7R!24-9&OE$B48y+U*dg*BJUoXHa`$XC5i-jVC z3DVTjt~kp3u+B*r(V~hOANi913(6u(tJUo6`Yd}Dm4P{p$djRZ;Y)%G`b+ZANSUBVw(^1XvJyyZ&rN_lquOXIaZzJJVl;q zmDE&P7dfGknhT2P6nH&Yk%^?Q^l5O;c{sDMbvag9?;)G9U88lDC~xwD@<^$n^v7$qX(9I@1N~iuZj{Vu!UjVNqda33G;Z*^LR^(5 zvX_qvZ;JSk9V*A5&xB_kij7~7rsQHKSfSqiP+WGI1{RFgMmU`IsMRG+pJMOt9Lw%U zp6si173u3ueP*1pZelM<^`sdpBvu-}4<2{oT^7m{-;K-Zn)4m1w=42r&sfvGp|m0b z>b}@irISn759c)vtm!t=fwPevNscNl)s(#QkR)2GfgT+iKAUEB)T9KX0g7|+OG`Wb z4kFn{z60Fx#-Vzz8rjCK>xQRBz(VpTjAT-5M@Z{B@ifT}33m_U$gkcSMKN*(PO{;h zf|I1cx>%2LDAdtPb_M&Q07uDl1C?`z&b^fSHKXu(-@vlf0q3XM(B3Rv1I6WD)bix2 zY&19H6AbCg)m%ToIEJ*Xm8;$2#eRk@tj1u&MflcHWAX`=&KJ|bip3MTm7#i*(@_DQ zT5>D)D<~94M7BZLY9s1u5AcZLlC3qnDxw8CjUhr(=2S@=KFeKWyQa&WKpZl_13KTn zQUW53Edd0u2^V1#pH#YlM7hScTH0I+kGO4^*dsz%+Vr75pPFf9NHs>ZeXWHwN-bgP zDn`ttEBqh%Mh-&7+6na^9(Rq+Jr&^5M-~*`_dvSr$TeN6ySuw%Y_7zoql-E+ zAT>a;Bwod&3la(F)*ba@5qzr2;DI{?H97{)%r#Wgfk2U_&3R%TeIgJA0fJ^MOXy?b0D^-h6j9w@vV~OJHUGe4(iQr{p zWiOMbIs~+k5OpA64P0B_+6HR&ndBE)ed8lRrfGs540_dG@ILFG^(8@YcScKA9v zn#e-CprOZg!em)VqgUyH9gOU$IsMtXGja@7&xIqNk3N?UN0mWNlqBm_TCaWtj!x9Z zsr2%T{DcX@6W_8oM%8N5`jVd1aV}4WffF3>r4dw@0x>wOk2#~&>V*0sk&_I#lnq%v zOy~5cvgfBvOFC)6V6VDd)jI+2QmrDbyD#G4($5R1oD+3Q6mQJvK6ea*8LW}{Dy@*a zfA!gJHJ|`tN)A1tk}tGAx}+Y7Kb+C;!cAr!@{?`Qed^6BN7C!#wkz#U8ZnQSwkfR! z93}_^>{TiE&vQwa<2_;m;SLXs`a(L{lI=#f*oMYNO~M|bx3bmO@x4K+R+x{F@dtDw z9GJ>R$JR7sFa@E;ILCeKdtQZ^ua+nriDnEw>l@v@YkC9U=P53=a)tk{em;LLAx@nP z=YF;lS?{%$1=?U1z>L25k!JT|J0v$@N?J@+_=B+4y8wFQR_rztbxkwIdNJ#a>Q`M! z_tG|Pfn#Am1_BTS!)oIP#)R-ml2EikNxe&7Q-2(qKcM8^Is2?r=GRuZ*L^3 za;G3>!F9ko z2 zhPop7g*-)E5$(z&&q9^Z8o$;kI4=V`6VdF{!>NZwQ@Sm7t1QD)%`OQ{5*w@51k1@3 zh^%Qwa%!+kbl9nm6C9Vt9ZJE-Nq<+efD5G9=^{?8VV z-hc7n%>~SQ>@(P*ngA=2o_UdVh&wGMC1kGU^qUxZl5CS>eH!gYlMceyW{i^?6Y0#BmNBvxqOdozzu z+mCcirXdop)OedBP~Pk}kGJ}HKU)>~jy!HAsvE`heOA<-b?{0l z;VZ^;ciLt}YCtf&!#5{WUXE^OYb&~wg503gA;xT9i-!1;;q|wEF1_x^9y=>l4%)obrQ zVpS89N$tuI*SpMJ-(*tE-By!Vb#XR&C}t8uTKo!)MQbwy!6Q3Qb&~qSjep;c(_J$7SuAV)Tmcw#Lp(rj`Nys z#zZfm$tH+J<`~MEC(ju6Ye4Mv7~fwDiTJDqs9+e*vv|9i13m1;<9NVQ#1Jlmi}whV zWKHk>E~d%Q`yZ;ty%AOVt~v~r!{JyX{f_9(JKc?FR&wpK)ZN5V3HH|cIkm5Ena&B7vbAyK+A!pgy85P20pxuOS z8}W+8O4aGkFr9L*0DOAcr*s?=Ih54O+}>?7ezUcuI)vyXMxad0h~s56s=7tAuRhp2 zBAC`}`;VNT%GfzBD3;XtA6*AG zfz?;bi>I61ic1npR#YzaJ{29X(qPq8R-W8*V0u`)JG1%3taD~y97ZLBe^1k5(5*AE zuA-~Q7aqjHIJ!9I2WD$2eQWR5>S~Pt@^UbGi9SVPA65#JdvNtS0ad|+%HcZ>-;wW# z5T{0Lx0vG?B3B!^#FX4r&&{j1p!A_uu==?mRXJAAu9Fb!V_Bl=2f9nB9^vyw?powgKJ4Iw z(Zf!(ux*mO)ojd2TRpL?`Pv0nkP`!+BvE?G4fY{k9hI`P zTuNvnW2ZY>2yI77Jz?wi9!!OlM%$a(Z-`&xdL%RYvJp$gGHaRL6;PY4b_zaR*00V6 z1Ac!W$LxdCayp3-MDV&7w;OgV3fpb2P+^)GjVDqGYbipx+76DU8;fQ$Rq6X~BVwUN zGIt#2CwFG-ZhQkN#XT6Ixw&nl`*d{k)}(TZ4byuk$J~V~!n1I`u5~rG7TRggRqbb?qbT;XD|Br{cj*fdi?0hBSXHCTay;y3Gdm`uvV%FQ98Yjn!g(JPj1Dz z<_HwoDI&7dGMXI1Oe&ne>J(AGyFUGFtZYtdDAQ#J;8hIpAdy4k8yi%&zk#GHLUZ!; z+Q)bvmOmpb+qSvdS!_~1Y+tAgD+B`@G3qnAdcbMe_7S=VJ`mL9z!yx!LR=ICd*fFZ zY6Z94HEXnPOZM3ff2j-}<`meB4LzL(^Kt# z(Vc4n-Pskj_VkTv_D|i}4gMsN>4gZ3yR#QN3?$Jf<2#OFPL$D=HtX{6Op%XqT@U$}3NxEI(Nnv;xZ3L*#Fzf5#T<88vHnE3{B zxOmlZ!oSt;vmIZ>+ktOR!#V6y& z(z2EQkVD1l4*$P>Of_?eZ(f@iGU3jybG~|pmj#?aOmg2>+nRgyPnT|oC8y`hvp?C< zTCjU>zBf2sIaVti9ZJ1s&=wyEx17@(Bch1*pi?bVSP*N@^J!R-{od+EQC4An?WXYu z!@hf)LU9F`Za?~1bIN?UQ{sDjdhu^c7pF%$o?-sy3cKr>xuR{pndTp>^;zO=P8dBM zd%G?u%yWOftJsC1e1t)@%NTPu>5x)!6bO{PR;X;haHZ8B1C{59iy zAKk|X6{kKn|I=gi?~eZlxW(h>jZJ26gHv_!{X*j#be$HkHti6#5rf$2+}jEY{*iQ` zA0BgffX7_dF0gpwEhB!Sdj?rce@Pq0U`r$`0gh33TO1ZfkW|5G3Jb;Jk?u}!*X{g= z_X`C2_`I3ya>5Od95-#%EOOtxHvXC&d4fX?bstP@Yf#uoQodgp;nf_!!t`?pevG`K zKT%4h|EoV2*uO)zmcB}lxs8YI3+mDifn3{R)zo?e2-1_-@^aV9U|2!!2w|brizT~v zf0%}czMc~ee-;Y=8`EPSJHA`Aau;-zn|h|zP4sg5&5ogRe&E2=n(i!x_!_%jTNZrD zjQ^TEUBe#;N!w`VkmtX6;FbdJJcSd#+9I!Bi*FkO_7>@#E{}^E;Nr6te>Lt^v3AF2 z^`|v~O@{WYU_X;4ng7V%SoEyfQoG7r-&8HMD1fN8M!ece;HW4PuuDcV=2MYfif6fN z#If=9Fcu3F2K=6Ki_IntA8+SmR4ZzJWlw9aXTR4I_ef=^YUAEF5Ybh! z1E^49=Zw9TfXhVS|AnFdi7y3xj?lzAMPE{Uwl-oAz<#PyM0t)@m$voG<`|R#tiFAK4xYfYAY+o`U$_dH3K?QruVt#`@D{oa^hK zT>1wc6n%t<)^1Y$+r{795{dWSOt|Lh6Nzn$ zRkAxiY*8(#q@0~lq@T}UBK!Xq;s4M3MkSxJs4MgJTwNeq(i;yxz{S`!c&&PX@<%%*? zfi{4Xy3@RHz=&5IeX(%95ucgpU^et=;DzQ%-`s-{4fT-+xwSGrI_|`$uv4A)Ug8(d zr{meJm(YNiVHQCtwnSc9GItA-%@82e`QN2;!}Dx0_O??5j5CqEg>wY{OHelIOOdh4 zxZT01td;xy_I%Vt4<$<9PM}nhFB(Zj) zrs=-WaT~qbDmsgRx<3o?c4QVl5bE6)rw^=)@Wbq_AfU)=v+aN8&QKqvrPVfQa0>{) zNUNA~)C>622aZqsgPFBcJ`L<|d|8u8DOc2|dIkWL^X~WagS_VbxOS>TZ3Fn)pq!3B znsd7VN0tL8QiCq+(j$gA^J;`ynbWI#|B?#ew^Rky#y0}nH{C~dz;Ko0i-?Le#QV`> zr7iADk>NER)?~m4L}jyI5R+JG0R`U8Bl-Ps=)`!njm^V8C*E(xmqPb}TG-{MSaeD= zi8?DrC*>xn6|M3!6g~cU^{Lj_V3<8IMHF~=BV~8hfbTk5GHgKOFrar2QbarMx1|gI znX)Ra$hVc!A#koBq{ZY{*1zB8=-2oE{H%ljy}4gB1NT^`F3_!yeJ`Y~0KP{DZ5Ysw z3~3nA@e6$FTm)fbUKti!iHx8+iNet?}h^x8Rzt7 zUG&{aPfEe|0@{Nt~YYudK3 zkaO?${Q};wN*;Y6sEwS-@0qUdC7|s`>P-{{wvIE7D{-O3=gLdZylm~CiP(&87Dud) zRvTGmAGhfADgqs=%q)W_a9%w(}=t_D1Irva45815QAVk4`|m{J?>dQXFU8(R;WAxrikZMtE*4{!x-?gX4vn6 z1+yChKTGPx#1#P}n_7^AptIWjkP_mTfDPUqt5yM6IA`cT3xl7;U1<+&0{p?oP_efB6PFXx}hO_y;SCZ=kzy@2bd!9)ChL zb<00$)UF@t&bsyV#LvBb_+5diof)(9!GAFhxCQzoC`az7qiO=;LJ281AphYp|KU1W6jcoa*8U|Itn(x9-T2(WBrtNB z$^>EZA~4$Jb${WrenTyyXozn@Q~4Jtv02KfLsYxGqSr_Gex3-h3zhQSO4<*|kfNA# z`k300+V9&@f48R;ym2Wy2iPnDcUM}z({UBRRf=k7zsO7p*cDIkxAQT+#+AIf0c=MB z(d7P~SKg==b-EPS*)X(l@tHdhC?2Z>BB*Oktlbe`=o8O+m_J61SOKCOk8w>8e)l|i z@mrWoj>-@DF_Klrn{lptnhZqC7ZLpF?bp`o%M=25bUR#bnaFzhc!zwt zDP!GID&jC0jO)t&C0y_o-%4j^QFc613HBsB5~(By0P&$wy9SzEkBR#L3<423;e-GlAL-1M_%J<`PTy37hqikN0=EHRGqfHlS z#V;3PVE&j4!8>5P-^^Kng!yjLI2tg@rBg#Yf_L zo8;RmSI_UVd4Nf3%GT`nlg!lfUFA53D6i$411sg&MBbX9u>&`?D@- z42v%SmLHe0*i9HRA3XivP{5usDFEUpUY}8K_AtPV_bC2DLFru3CH+3rF8_Im#+p%% z(ZerJCwMtS^~w-qEHfYw;@;ooM^C3FSW*0sFDEd+ea5zMG`j z7>I8X65hH)LrccMbC;iw`z{xkkfer;kf^d47nhu=yt1~gp%IXD%hJI@&t5~{@FB7i z6#VPgZ{57bN=(f9P?$^j;XnQNtr0|k1v!BS87q0-^@`h>r6857}Sj z9||hk6?6b7l*G8HW`arQOvv@* zO&S)xROu%o^}$^RZc~@1SFvvqlaP|#W@KV!VddfF;};MVx_4h%MpjNB?Gdmo>kk(rg9lbe_Sp{yKMQCU@8 z)70G3+ScCD*)=pgGCDRs@p*C{zOcBoyt2Bsj@bLUe{gtod~%9}3k8Js11#YGKZJ__ zfD84?6|^guNVrf?J&}MDTtTPh#JC};hH2t_laA{N7NOLew9-#k>ABT+iA-Gvv2QW( z%-=>JLHiEbe+;mv|0&4+0@z>SngrpXp#YPIMgS5A?Z*QekU+1>r8pxHwINSy=C>;y z)6W#ekC8D!Sok1%%qV;iRnDI~=kEwpO@SqG<|K=CD##gFoTFaHC6{LV>$bYO17W!; zD)0|r?3nS)saGbM(SRo?$f7@j`L0k`%apfT9TeKoE}v5QO=2aQ-rIK$FOM0HJ+f)qL+W3IwS6rga7gIYL1; z--q#$`jejm3>fsrtp8%o=6}2P?Bc)fASiYK@*tBvf3{FCDk}#lF@EkoRZF!(8GmDS zBkHgNqD!*5AN=^XQ*;$dARSSYb|b;m`##}Fyu|ALJGwy86AF089w)qOCzMlB6Z^SY z#(6`2b`n?bG+u{_inGO#+L^LI&}}OAaW)qw)FCI-6bmhnZ8iyTt|O$L=8bnXyk?7^ z7pNRGrI%l6;A(JL{rGkbYYvTO<>`E~XvK-KUc2QTmv+Wb$=OY&+jg`pSOATU^eCmr z0a_Ur2uZ>K3h(~~RnZ8)qn@fa@I)#7Er83QA5VS~43b(w@IUBKe^fC374GpRfNkH< zjo-1(3wivh+x?`xDpplR9h(t4Rb4D+qOIhjt(-XNDP_}YWz(u?%sHD!g!V^-px=H2 zaX|w5on`u+CH|!far^}RUlhDw(EW?!@2?5|-!j38DL!#lRau&!fRwdGLqOn8N~3%s z33`=C-hWSuciLeG%-r?rR~U<3cIV^%$<~Pm9{p$8IR|s&{}R#gi?sfSk{15>noQf} z+IcQo{dd)d*4NswlE+qwe^dJ#=xe8KE8%vlxy~6;!}*?`SgR8dw=K3%O?u)X(dCbu z$WmaRssDBXp_<1n>A)X18Gm`#@WmVBA2-%NswkBvHu+WEud(}uDBsEJ7ghY?D8Ja} z4>|KoR{Ro-zXani!T3us{y(K^<7#Od=5vU$jQruGuxh6RP6#^*(`VoPDj0<@*IxTo+sWduaYLfkFAVub3%_XL2b=x#X#w6h9oT(4>x_Z=Yp<;jz<6nZ zvjR9sE9S4yuQB+A1OM;hfDOZSOrHmpsafJ)noN1wbOx2F=d0a5_VPYe>+R>g(5 zW}kh{wNAaO6uVEIMCv4YGQy)u%!n)!29#YVW4;z`QIEMIqdySfj!TH%`2i@OfqXF- zg1Yz_C_jjN@t!n_i~V2g4yxt@%DW+7R2`J~)zGh@`-Kd@=m^PmesP9hO!Y4!=od2l zUqS{=p5w~{v})GGu%Tg%ZHPPV-}r9;rv(8I@n4_+#2DZ@)v?|j8R|Wq{XhXo z(eU(t!2!yDg+rkK5-|LqM7sEer~hsw6O77PqO0q+mS;_AqDnlBw}BYVKY|3%OX!#?=65KH2NHmGCi)$Z*JQU)KA$RG_syi`rlZJ}ONJ zbL;g{J2*(a!h|iqD##m09a{d`NvtDk*?xg2i~Zaenc$Rm=xtz=W8g*_kP~>s-B*;E zrOv;$Msv*b@tg!xg>Z)GM(}Z_xTj-4%zzh(S##+Vi9Uc)0eMr39Mj5_zLsd^y)}o| zeT6f8ZNDQ|{DLW!CuqlEo319Z=lPPYUgvH)L3*3NwA~A)EYNMjT4iU&()zrhN#>j@ zzym&rXYB0AbAqv;8xWQ$JZ=fx^u+7&*aW~gGWtSNX?^a54QLYJjp&4_H;3Z7ooqo4 zgz)F?*#m%E$(QF5%qZm=6=V(|%pmzM*acKMHawRP9C4nekH^dH=jm}jtEf^z;fY=! zDi5!I=E)PZ|I*MTtS*sx2(5Bp%%mb3D9kzD4qI}X5#^w_YRcFu3P3N?Cu zFhLH4nP$-296vnW&=`}JDq#Se&Qj=nfZuTz@?8a%S$$vi>#}+v1>|d%Ko!jDKI)rd zBcMsUnpRRz8s-h<9UwvCVmu{fk5Xl%o`n6B@sts#F1a(MZLhO99=|+VEk&iPVJ*&0 ziK`N%=@~WCcNWbBXviK(oF=8KED}>$W!ug+j-YFv9wOrQOebn zCv1WySz58a527lXxjTfLem4VZHD!0|KJ)1*Daz}|(ni%GTYSBE&Q+$>N#IZSlt$ur zm1>y;GBLHJnMoTdb6KT1Za_TR4l`N5gOQ3efTFd-o>~&x?J}jR)xcI?5Q_lPZnUIJ z__LtEa@J$x(+Jl-PkDz%*9)`DEjbJy4HQ=BVU}HX`^U8ks3F$pEd!Mn zi@ub8D2-47rPP#KWDm@tTiY(`*aLOVj4EdQIa53^%T&y0JdMmbL^K-Kp!ByaqAY$D zhp8BY3IHlR4Psdrf@a4)DWG(mp%E`94(ARGuQpuI$aI-#s3QO45B{?Z{$QVXY zWkne54#a`BPWd_=;O?nB&U2yFrKdm|QtzBG|E8bcvid?pzF{|OHI|h8k+_#$}{6Q8I)&Q2xCY7A2qFHCrM3lb;sVQKn5PsD61s>!O(j@>IBS6mK zIRdFya#xbU*5{q?SV!R8$)io@((zsnZ`2O+M8j!>$@Qz8uGisKODh>1JsC-&)IFf9 zAw%voKOzK>corZRmR7k9C4d105b*gXbu|y+5(siI#w#iFk`&1*LkNd zdLE|Uf*5VD2p^5inIzDJP_2FkyC$sG5jCgCs*zPWieyi2x#sazpkwKYn$I926^Geq z)~R9YKiJm}TYVn(8zi@41{(EfvStRb-+}Mp4U(Ry>x3F1dP{Y_cLvs!mOc*nu>lD1@ujojuiqq9#*+0h|Pe!%A5EYKn0JbWC;PD0&u#wVd@${kAFlONe|*y{=rp* zO|l12IkB_e&CBXRF*592Ss=tX@Vw)!rB|{s=?*y#E`fNjdR@UCQwgH-nV4yA>4^(C zHmT;r0N(_74z0S-5DGJZvW{;X6<9nH;Xj!t!82tnB@o}6P^~*Yc;)a+_iVY3=1SB@ z!9b#7k$c&l?uS}$No!2Y+i9rUs=VW%d7AthQUa(x=|%F&oGXj~XB0L$MDi(+IXqDl0uUsCvU!x&D6I9P2lcTc zN1zm+4;g~3-hp8`DQ5vB-AM@{q?wr6t#rL4QQR@2ETj7JQZ<_5lG-*wNC&(>&8lJvd9cY!PEKz#6Q>XX%mq+2F#|ID*ow+-sQ#41oC2avQm)D7e1mW7G5*+Z75*7OyliyuAIr+*w9ZZTS`hH~lZWU> z?Z_FBW#}ZHdn8sq_oq$3&&gBB02>-XK{Mqiq7lZ$q zha;&^xqbkm2WN07{6Y==iI>VfI*jRQKU)<0K_Y;%{7*b1=DhiYZe>G#sH8PL-FqRt zWE16xC*=ns{%N&qsQ*o5&3}{H{~b;=_I=gxVD7sW=6MU!6&q<*>lqJR7^+ScShvgm zew8*`pq>1KjV$)4a4Wi>xWuA+44ZBQN}cB z!VD$WoYqT@?*PayVCDQ-R)jgV`xTsL)k;)gD1xNxT97`h)TaCF|VC!x)3!B z^wK8sRYhicm0B9da*eKg5G#6HP~;6;7xBZD6ZIyALx}OcQr8Y;-Rxuwy%Ljt5l;A7Z?|1*BwK6~PB$9&Wn#pR0cZ z={OzhNPY=CAvR0JJp~kgNKHXERRVbGD>H>@o&LkRks>B>M;t5>bDf&IR~OB763*{q z-KCv`)EyGO8+<=suV_Hb(em`36F|a56JG6VwIxFfQ8R~~v*mTnrQbkiZGNymOo2k< z(7-)h9+c2a&brGUxpYRw97<{JVR!bM26OkrEU;7rN#I52LPK;_mNoA%z(3sMq;TEKfkHlw+Qks%E?LzbwcMfK_?9#^$W|$sCIyAm)#)F`H zYfOIMxoP6zA?eUSH_Oygga_2bEBm~b$(^oVSauj%$a5Se@OIHfu1FTn z7l;44l7L2=>S>TLC0I35>x1IEDjXs0z|eDPjF+~bn-!Wt+B(ezNr$NH+v98ty&Le` z+~zI#3ZS6lXDvE-LsAwK?ey0slZapPo(V?T6?1oKd`0Cn3YmX(!MdRs*+Fo3@F_V_ zhPRnXzJ56ly@ja96A98wDg@ETYU1r<_jRzTj;^T<#TVvYi!~$i4}+OjunxAd6gXJF zU`sTuOyG5xD*bB&W_VUo{O|*f+e`KP8&lC3h28`MkduH|hrw;uS5!T^|4)a~X%T)kh};Ho;r#ZayEhs5v~VcQ6cYNlN96c@aOkdlbH0@lJmoqR82%V!LkvAyj>Z?LgsHgx~&=D(JqG zL$qClGCIi9V_4RetotU0%nIa7ofC9!t>!6Pd6tFP&H9beV#)nd6 z2QkzkvX*+^KGrSZIbM2UE*|P9ZGW$4h&7^`q^P@5kGD=+YWY4c?aCYdS=!;P9iF^< z_krLUs6pI*x8dev>3bBW<`gM{5rkU*bL0Y8Cb;X~G4=MfO{eC+72;yELR+3IM z3V4uUxN+-ba$3OLXOpqp3)tN8neN3mQKDDyk8vISgd2uUHJrFolNw$Y47~d+|F(xA zxtS29_1LWK>6fge-Ifb3yUKMGN_^g6Kg#Tj%B?2~ok`Q$li~S2d`Xc#LNQ7Oi%JM1{n}C^}<(!0uMM7goEoe4cep01Qi>h*Z{{utq!FA*0O{ z);GK%@#;gz>lX9ZTUzT%-YYNt?0AMR;uJ7l5*;Zwbl`Zohq2;TQnK&cq}6xDUYNgT z+Zrh?4|z>8z?~p8{xYb4r}T2mEtv?BaO;*{vLD>JYTU)V1YBNDvNl<_(k>*OQ~*6l z%xe&&RRa0wnGAa^$#w89>wN>gs ztxy6P3?0(v)(=~*ftKV~Q0t%FkMb@b@lCy-=^DTEkvyN=avIV;0@=nx`2rHn#Xy5P zCkxk&Y*j#mobFt}!DYElPsv&M=_)aEQ)OT18_dc2ScG_{BtI4`R^ByS&hyAQyU&4^ zy}r0l)36&WA9KZV&8S$IfiIjczi6}{^+_3d3)OAdApN5WZ+uB3Z+3w0JCpTQxVxBw zGI+XQ`$@u|k>60|<1{MBT8pi5kC&!U@LB;9tF{fd>h-0#MFuc%dg5>zMW)-uPR>+C z-9R&o$v%E20~@_VA)-Km#{urG6BMZt5ACgh6IO6aP}NI=w!_IsEFq>e4IYp-S!EJD z@YCJx`rMg~AxV*}49q8nv%tOx&F6sNy8fnyJ+AJ{1KOniFm|TWA-^fh$>R+JqnHKX zclPG`Q5K22u(9KvONo>hk7qH4(i=uRd${tgQKB|x8C-^n<8zf|yY_2Z=HnK8H*)+jng2!@8burAF#4p^Eu+;YTCr=r%d5}U(yw*Kx= z-|Wz5TDfSDS=M9f>u$_x-$06$hM9Yfs_1jU%}2QM3;vd8$~EB*=B(FTm5=RRQ3}6- zLK_fA)Mq0ogRc^%B5}MCoM$rA%O!P`aB$}VCafXabp?%g)awz(Ac0&5i;;J3(&#CzOCeI!Q&^BE54Kuu@;1eh~jJ{T2y<~I`d;TNc+%`n`zBs2qD=NdTc1GU1 zYcE^Ni!@5s*u3I(j^po+5L6lqs1zx#z${tSS z#h}`8$fe{Be_T|7{;L}`;oF4>1I22THKP}vA|A0@JkQb9;`ipR1a`Qn92L|cER%fm zKA-R@mA~ml6U6mwtJE3tCQ~S}5Q!m*zJS34PaS{&W#eY^;yBIerBSDHJu788vbnZG z4lhdM?_E`!Fbxtpl0?L8Wle-=PJ2?riPk*6W{q6qv05@Q5`|xAsEq8~vb!=N3D7Z9 z6JCFMbJYq2B@x&s{02Jy8nO=7Z=~L1hnk2(*&CTrFlql$9IN;H7xEFu!Ue+kc!-_i>cMFZ*@>qlwXW zKbd6oq38Bp=KDJa?+`hmC}+h06l5LM4b>wD%qi-3h~A;SmPozhDUJ(&`;hy(lw~ch z!X+Eu==Nl4XlJW{puwhcS?ONQBMB)snL7P_u_B6d~sB{U+wmX<;b^IWJN75 zFxC^1$X|WqL)1}xbG#VJ?w(myY6#JFI~^}xEi#r1t2BIzCUT^S5KA+IqXrxAyE{rV z=D}w)RA1wAQm`Jda_e5wNA@AE$*zyCQ;`kqngG)P$6>RF0@!@C(HxhAwhCDTuz&2qZAx@C+t?T7g{9nAPVbrh$+E4x>&OpluD1qz=-D`ohHibPV&7i ziu!eDF5Wno?Kl)O?Y7Qj#n$!b7Hw9D+1|UK0;qlM=M~79rz-alNlP!#YYN3dJQ~g&~`s2+y_v+E~>_3d9HfP zyhNW}+wx2MI_(>HC;_AEm8B}K>32LsM_iTANhPx>aCZ z);dfvp6oSjI0O@RB`SaPAZG^!koLIdW{D_}0oZ8~NOx&!a; zCVCWw!12k$e!u*1XBAHda>E2E`cZp20RxQoUcj5ZDcRPSCw>}Wd+ECIgQ@ixWvVFi^#GzRTq0WM7s#W~gaM!lD$|EDAx1>^t#tj`YdRr!+ z&lv<&hfpVUuYk$1&b#IkLmd4;Uf$-S<5>2j^O=ky?OJLauRcl%HfVn*f8LIveZ}Ds z0gt*=g6<=Pks`d8!8$%)#UV!OGYKG(0%$y_XF7P7hL4Lb`;FaW4W{zWV6$EUh~jc; z&OYUyfS4t+OmQ2;P3ehxP&ibj)#qcHnA^x}nfehcUiGQg_}1`A-VMPAdxZdascW%g zWcDU>>OF!CI)Vs%#ixq2BG&mE{eS=w71mbLm$fT!4xVZ}DrQDdZWoBQy!4`wArFfH zmmE7e*Nim!peY|mcd#=>uxu{BS>z4=AG+z zr8McbD_L|zRRI-G;}AnT8X-!@q*vFr=$#NJuK6k=8C?CAuhVpqJQz;OoGQC`(Uh0z z<_R(P8hJOs6wIihuN?sFx4bsD*-Pr^q<@!~f$R$`kYt@Kri~}YU2CIUM`Hn(Ty>0| z;9&**Hp?v00F+1AR5|*UgW~PZHYJbvqT;Nz*WS@5 z4{}Ocz3(>o+xSCow?a16n{&p3MDs}G$!hi;?U;vnuW`gDcO+309|cZxGc>~UK#1r5Sl8tO^V;s({7~BP zrS(XPI!nWimXyzVON;D}nb8tQ&L0-o^7lIvlb{hLPwdb8t#fganIN#wD=bZJUaCo_z568%VEN1#_*`=%SRc@O?i~ zl>o@+M@lF4CmW5Kx!YQ^WA|UZ!~&L5fj7L`D0!2|h-UaWu!`8vTw6X+b}w&wd;pEt6Ys*KU7~?#6r#FdieyaoREHGe$I& zUYEZCf+x2C(`Deg2^e|ugNHs z1->o(iY8f#tU+~DG0VwBL*oXci^+Z;g8O+;u*O9c+53`hG={I~39*N>yPXxYYiU1H zaUQDYBlq?7iWQhw$`=L4ASy$k+nbZ?%uQVxBG{m{LMPf0nun>6w5s9NDpc#0@zyFI z5Vl53K*)x*R-7r`wOV^)kUK7-p!1m9;b2Nzgf{zbS)sfu ze@|LjuTRa?RHi8{wtbtBD(1C$6%vt%I|tMUpWBo?pT*z^QQ18iJ12D&E2;D#k)DZ$ zY~LA^+oF$fG}OU(aue7iZCvrQ^LNf!OSUL*@N-9|0)9JSy?w;KJWWOD3Ec^0B9jvv zZ9gWLB73o#9AIKJ5-fSQ0nW$9{8gLFMa+oP(D;SOBX;b`S!ezS@tetGGpQWjEM+7V zI?-MQ4}>salr0n(_6P~8+9&NIKI1HK6_R^dWm7V>%k&_~_>d#R$AMeY;&m3lx@Om5 zda|>2Bufo%GkgTANoeP#`R(D}$>}h*I{~-H3zy~Zh9&EY1d|l&K405y%!OiH_thbV z&k_ZuD+#xqeqzoP~X{n zTReM0Ow>okc25V>#LghB&L&(D)58T0~}XNvb8)`vubZ z!S^{%e2cpo^`^bW$B~R0%jq}Y>>+gO(9+wzoey7LT4*%#uIY@uRy1QUQU?)7Fr&TR z-3d3dV?1+4y-%@HZ)Q8oGP+YFKddf^wW?{w+kcHfJal$|f+d5}jw%R^X+37uM5m+W+I(bvdaoTHltZ0Mn`o!oyHxq6+lf7{9)zSL${$V-W zwkymU?VjEkv|QgZ*5#Vni(=jygmPuBp-3#9T#9Yw!D9*K=LZA9Kzz=A5(k-fMr>+H0-77Dyy5;I3@`8W;ua z(pZoev+}tIl78g(Ty*O26bfmObu2b4?z@%oxX5tSzGCYB*nI39Br219>BYk)2Bcmh zVaf-qF1#?vUda{J8^v*~rKt*dxDC7V$fBuw6<*Um$yTmLBw3nch=JM`546{tKR4%n zk^LJ>9QBL6(O1yH^Sa3wV`4wI88{Rsg^;^P;P*QS!ff-vqXijj(lzJN@ii*Q@A38b#dw`q z@~_^H`w_MIOEC`w3359q*UZ1YSB+wBHdHBE^@LR4-JaFe7nZw6E8u*nzRm@0+-RE z9+?pGUL#rY!fe)CcYyT1(0?HbFRb1TN}TIh`-~O48qE+J-LdxEEB&}@H`5tZ{=v8$ z&U!skQn+-Gw<8YKyg2y_GA;Rz=IsAj-Kb0!xZm~PKXZU>F?@|=q;Y0A61(H=CMT*kt=I4fg^IoCc>~t6GZBDuN=6OrB=hzU+++m(0 zh200LtUZ$181glcrYwz1V3ID$-q|k7{BJJ_(GC3~{Fy9?zB2!9{oe>pqOY*<9K_x{ zBP8;Sf?*5Kq1bgPf;y?^l#5>Q{m6p7U}to4OE)U(>m|--vML|43I)bRz1C$L{bqN+ zAsE~o#Zx~XM1&*{qL&q8*X87kAUw+$?{B0Vw)t5FQT_urYkFaj9OZ$QaF9=_6u^l zT(74)Mxf@)RXe^XAnxWZ9h|*S?)*iJnPg4@;MU)j`0po z%<}mw^d>Lx)BC78*V0#5LvghNI4#L7h!$ zSs8xkHK>f{$U@W0_|DCCT@44~zM07FW0J$UGj1kW?K4_ zVOv2ffpL*{T58uz4+rl|MQlS8VRzZsfB4g*3NqI^r`omdsvp4<@^j_ANZ#QJnTzS# z*3|QjFX_qO`5T|e6g>FBO34f+P9^_8nMuZHd_45~_bSl_U?8z%x(lg{z?dm#+Rf{b zs`#2sBKQH9KZ&3tzLft)J7$54E*Q+Pns6qj#>nSdtneMm#anl_j42`uZ+&4-@-ti4 ztyC4{WxnO-h$4SuF}u-E4QOKd8%+1bpE9Hg$Mvy{FX9w-$v*!P&`Py%j___G{`=LsoS(!V_qXH9%+<#4R!3!as3B_s&wxU7_LUl&2Zt}Cb&-{GU`H-h? zsMylwdCK3|=o5G!k>uqH`@OMfur|26B8R=L>sXI#RZxUFrW}oq+R|^<&Csmy2ef3m4nUc6@(#Ifolm)4BauT+*NM zN&mSsR?&G}ds5SOo|=~G3ttA&YU+rK<>4Va?l0Hu%ypXWfw|bw=?}- zC-W&iDVPImNrHKde%Z)YZ+U0J-yjD43!)WpG1Ls?rr`Vq@!Qj{#7o%Uygi#bi*RwV zzm<;OQ~TndW}NSiP#?8VzWm-eOWXI-sBBaPVF>apHi_pXPSUU-80 zHH$~x9T3ma2>PK(7t5Os;Yy%32f&sl=um@bUZnE3}35YaW}xkPo^uU}V3o;P?WICX-%~M{~Ox8XdB364MCnZ(>e0l0{{G zri*?F>LCy@4C=DAs~zI$&b%xgzhy!C$DbGhLRec~t8Fb^#|wxB;>Pv?MmjynwHNMB z8xkR=?UL7DS=QGNQly95)3j5+rg;_d^3K3Ca}F|*o)cHCwgx7G5-0j#&%;n2KT%=j zpXQp4W>Igf0ZyOH(g@tobf!a(x_b4-Va2}niF0);h8(KlZ|dI>7pA@MNM zxvR$7rf1;^CG8A{xary0>?e2UDT+$2p{=-|+>C)0(Q#d$Yd-O+)Q30N-;FQuiOo3V z?^WlIj%%&^@mf@_xGZ|JjOyc&-m6OSt9p>~)Y*`qCOzgJ6uB0!J3q&VFn_cYkx1aa z@min7$l&qz+~-VBK+h zuk%j}1!V+9K()j_9m9M5kIf$@u9{fSF&t7ZfyDoQA|#=p;*_NfT6>20KJ3YB36vqS z-@kFFN#hF-G#Twv&}P?f&2N9Nr61xcYl(h~lQC&QQg1NlM)XBm%SbKSwdQd-R6h$> zZr0C+GUsK|iV@1*f43vEqxha6-2hPSj*uPO+x`Cdngc`o8P zk@iUjS1ym-+B9@+zw~p>24`T;l)*^VCB}qZN6e~`nB%HVg2Gqr2*exo^!S$vt;naP z#UxB#dhgMXYloO;5ZHc#Hh8EHDNI~6d=WAHD)LP8&3MHb)brnbkB4hgqDi22KD95d z-hzJDM?4nuM6OGkr_sd*kLx3z66(R{K7D%78Z$sUEM}T-d&fhDrxwGx7fpx`7Pp&m zzH(!eBaLO)CC{anZZ3*HFn}v;oA?JjRZcCwJ`7Tel(^5sq~cmgLqf8isM~v^4933K zM!n(7Y8&a~z@u*6yRJA(a;1I0z>+`6a@Go%Y|yeN^1mRel3V4fu*>cN8lmLhJSlEA zCN8amP;SSo=vHo;#qg`Wr{fT7e}R<7n_smXjI;L0+uh?Za-iajXCqfzUExtyPnkSo4Sr3*06puGdp6R!@I) zFqQW-kddK3sGcnkzBW45A%`1Y+{LsaAK40Pj;$vM-7z)DDy?}eFE&W{);V>rgkcP0 zO2acP0n9yWkD5bN%RFwzaG6IzzwEjCn0MelBY=GGheVr%W5hpzN^Dwr8da>O(jWw@ zC?8DjyQc3;__s?=B0l|sG@2y+FJCEGTP_S7VqwS!yoGQ`y6zQ+bfxd$oN%R~9fvR# zSSvH_zVV9~LC{-^ur0kp80ir(6(TS)vEXqsS4H0vkX&w>Ib1g4^=n?Vh?B@YL}b{O zQ8n=Ol;+Xlxg@H`C=1|(9ITA-as0mqY-ldP}_CC^+w-<1GR#>26XA$sk276--HCfQhwBRpc1RO! zz^k{IuP4w?p~E(C?Z@g7%YsWYq=N3*Zw&8Glp)A2)65WqC)1$ym=;J5slfX-B=ltys zc99`k(x~R0c|8v^TWXo$H*X1d;-~^rY!lR&n#e@wu*xiZ1_>?FF(hL!`9^3jziUs4 z?6%v9zuA#q8hT47TywN5W+I-eXWhfC>v58uR{ZyPspW2 zxwO1o#G7?O&ev3F*A&dfZp3vkmkc}^HdgL2xM))PMD=2P%ufo^kZvJGB`!+|lyIR4 z>mTGGTKhwpx9Ug3)gz01mdMQ-9Dz%VzVvHxK1Ca>Pg-aR5vub8SBZ!x3yu1wE;{{! zkZ`A|S}CV`XV(oOZ!!FI4?V6R><#ELL0EX@8QiB`-Xhi85ThzEG3K-*oTop00&Uz5 zZympNf3n!AA8I@;Z;6#K*P53>LWQJ{CXOtSy>Lt4!dh@$#Xf=LSxEo8fgcXN5QEF!4Pn_dr)7js|ga+hJ%_F^kIf>1M9gF2|Es!sHX) zAZjVv%zz#T=a@-5>e7-lK=YU0!E<1om#v#3pZ>09^e>(K2NeF!BX)p>>pVN>WfB1Cb2>k4O6_=KLDNhdzhAcr*UVz`b z>A{k|c6+w4(dJ=4>li!99U)$zdR2BHN53w^hI z3=^#T&PT}*N#vXqUCi;=XKx_;)YA>Ns{XHnA^m{khf#D3eHszhq`pSz5g^4#%I>>=g*Moi zad=Q(#ECOeTkz3w6_@3UW#D)ZYaKp=TxS@r-J*ka-)M>-GK6&%!jzW1K~+D-gd=qplDU#;7(gl@bm#-u zbtI@ymygCuLK%=N5*37l+axCedaGLQ2#nN@4<|#&;@z0r@=7V4leSlV*l}>?3LiT< z9F|Cn0q-tR)50mV8N+$ut8<45D>Yr2{$Wxb{%-dz*Pk!s>t1%tq=M)xwRv^NpZcN4 zm6K@cIeQ8w2R-2>|5WmjKtX74ysC7n%*-uSEB9#m5ZSR00Tj3R_>=dc_1zoqZ5?Pi z!;r9RYaDs&n5XjWl;*n`B>F=D;c=T$UmKd$Am}lWOK?EOBzQ9}-x|_Tx0alvb zx5uIh-P7z}$W0Nq<5A^FFgfK2LgcsCG192vwYsMUMFldvDo#d-wg4J6VV1BVq}nU1 zK^8rQhL%qMRPloVS7m6|T}~DA_ju$n4ubaA)U~)2mw2Bj;x-z#Nw7p4RZc1>))(0h zWPdlh@4fquS4u?y>VE!$;0pUXC!S0^b3ky|0^Yb~3 zs9T7SFk;D1gqPvkssBwbf;m4m&L_V6s;*5O<;PaBw~QYWc`OIGvhNjXD%(!ja7WqT zj792=N{QHaIvo%_QY~k6|3=OdXfzX1`Bvcc-uzV%czNdQNWXxbug1yQS~rAUJCaB1 zFoe8taJVXTF^*@KiMH`&jcLxie<9F#RH|`zmiX)NW=6A1r2F z23dN=u7OnpP;wezNhPMJ62@({lZX;fRM@H!R(7NwJivk6Itc=rp1Yn8ICfWnR^lwo z91_T4wu_Ht0kqk|rZPyxMgZSE0}GOJpPw z4X-gh*70{Ks5-ED&fH-?e3<|-4BH>mi6s}QXAEkQm#h_V#%l*ck}K3z>@DqU#Wbl_ z8`lSX*%l5=DpwxrTXsIsn_@-c05H5uR+TDGne1C=>6p5(@d%Xs-wX>ctIy}Ux+O}|AIV)2clUv zjsz0jK9LGjbwWowe`W;SMKCP9dp^#6S6L^9O5cSHtIzx1g2o$9>b>K)?eI@>a&A0a zUodptS{hDkP`JFteO!+l??u?czoe0m*A39EU39q}27}{wpaY+nHANlbVy7;ZIiiFR zmK7D63Y55hq9h>(Zr`7(Y}|`WPMPOD34r0x4E+qsi6_+mj1BBtubOu*ov>qVOt)kMa} z^Ocfw*O3fHJ-;A6$*NB{1Q^H&0&WvnHEPL?JsG?8TEXICS$77>tzVFrrtPr35zMiG z%)=Tc-_Af2dH; zH9+w8Qd*91(3+ z(g^%Ysk5!zXBGPX#~)DYR?BwibWKX>%!_p9PKuTOpFCIH1lCoqjkZr705#{21HK6j0!f3QSY@BSS zYT$HN{*0J{#q{SMncv*b$@|?FE#C>rgMJ44^y8%{#RFoS{eN+5=4+p}Wbq)-K9>M0 z(+j#&FV!v>vOb+J#hl$QF}-P3tFAx)Ccmdhe{?=Axc~H}TkM?QK#_SgF+1jtF|Jx% z8oKz!(wb}SW7_!-$#Fw`%a#QUfXp?}J!c(ycGi}J0y?5A0lfy#)oRlPysp7oMVe>G z&W~VU*z#G2g30+uhqljbN0l6OFq|J{_Q8w>!(jx5<9C0Sacs}0FHjX3i;NYiXEx30 zH=jNJWnF4tMr{Y%Lsh%ZT=Gu>uH4k3xzleij4_<`7I~#WM3tfcGWknh>z>1Y6|&@^ zsD~VDA-^E4ptmzIq%-?LLchoKeE97S*(THegI)`P{c-QszrELHG5y(6v{_qQhdy7^ zrJ`Q;Y4-aH&t)H8KCU&A-8Yp~wNCTr&2tTfjO^Q5MZilbZZyG>o*6>^=yTlYsV zc@--yt)7U(^7PET{G99?nVb$BYoWg&b9AO#VPJT{G30k|op;R{sZlojph)LSS6CCU zvz(nc8P0lJ{=8?;hd2~4=|8>wH?>bp(en38Exk#%3|}}^+YV0dl6n}XDd#cpSM~zohwBH|0I7lrRvyNA1JJcEn79DK5V(yAuD11!SZ3RE*QIC zkcu;6z`RlYgW=EGKl0kA8*~mB;eY=Bdu)^U{sUGfLpw{h^BdGz7AAz3O>k{Jl00K` ze~)|52$CPBCXYovekTs9WFNrZ!2^AJ%%^jY{&~y4J;;73Oha zQ`Z0o{8LsO+fl0(8`=O?qkqNA@~p=p{~;@9wtc1*k+^hFG@*T`l{AG_PxSYoK?y?B zW^td(s(`8WNF!P8fz6tMkz05wWGvxq{uhU7K6nsSQ~X@Hr@c@pty17HtS zD!Jh7OnH~%5q!H>+gl9ox|5r|<~8(A`d|RqmIZHXLgnrW=YBk!{{Lf?YW_RG{YEL! z0x_LHuGz53vdHgUWZfdHUq%6c5zPyZibkkCKB znKo{dc4$Kx^uUTPVM&1a`tu=b;rpT%QK+xyZz#_-QaK2t>LJSpIMqd!wI?ql%ebm+ z)H(Oxl!G{iTlgS;O2*Fe&N9FT689qV-)ZEuxyEHDOHgV^1HJGwR>Hgshj zH;zpYm)J1_XPIQ46b?NEsnQqJD)E@^kqcwV;YlmYaz&i&B-L3raa+iQ`FJP9NcB^A zJmwl)X+4l-8x51PL_0sEc1XNxPN)}a@J2%gyB0=o8|{;ggY6LG+~G>*G>L=C!b^82 z-71_*9`{(;1{GjGZ^Q6(yy#M5!FYCCGadz|f zdD}Y(dk1$us>g#YiZPi-)KiDBP#GUt+7}QX_A}RD3m$nFdh0==dgY_X7vFib6t_l+ z`uSG)?Qm0=UNM;Lz-KKW-nb%9SzO*(bs%IQaG$-mTb@zR@+D}k?V+aAl=RUc9`CHE zM^VL8HR+qDE`pk<0i;>j5_YH_nM8_JgvD5Vx zvGhBVkjE?alv9?7mX4GpcHES!HUmze#xPWrCIID$gl2^k@a?7>tO{>{mxV!)d5Crq zE%C;hx6G-}g!9D)uFlMln~IvJ(uVEx#x>Z$@FgA&<^GV`d`^FD`_-`FPa%1)YBjqR z!FPAX4SF(QD)^1b?4%0_1G%yc(@&2s+<8^mVwiOncIA6M-{4c@xq&c2RmMuwbkb+&B5 z_X=WMFX~;IDH{E4)T%PWoj1GjB_`R2YSXfgz^F$nxgERaP&E=-b?-B)6WA8v&6CXO z@!EU%_98-c=S3vmoT3ytiU3ymz2|yfO9@lWh@cOPX3V%e8ynLby>iWycME_rtR9=I zc0wYwttWR9@gNAvIVeH5w=`ET4kyK$tfw{I8?2R!yuIJad@e%xY=&)A9QZ(7wClVY zt{RlZ)OKdjFK2uKOuBbD2M5(EX3Ves|QlxvUkL5%f-a?V_hF1Fs7p>Qc{k`*YcBxlj4J zT{3!=?N%^T{UyltP; zi@NB=w#ETk*DuK_Q@b!U;T~uhYwr;8fq%5LM0&e9ctob^bdH3}IeA4wV+i!(4Q%Fy zYUK&N0F8Jv6SER`xdZu{V;z613yT+jn&c~gKK??pE=7M27`4dkJ9+MmWZT-9Ty2Ek zyq+A$H@ggVE71TmiuELK0WD>kjZ;gPbs74!Gy}pp-%#C7?Yhx)^ASt6mFkpKiB9&R z*N3?EunRgPp3vAKep#5*1xT-R_Fuk7h=J3;+<1YX3qW z>MzK+!PBym=`QlzoqbsbX<3MSV`Wu+)}+9TvM)8_QnQGU?oOK)!(%1<%jT)@60m9$ z8`;TPD8*Tox_I}&)%I?$Fjumc9dX!$QI}J*&07mECZFR+#gC$IF6$<13Njw!sg8|% zHx8+dYI--u>#cBL^`SFcHK0t{FJHcKE55ANE}|Xn=y3B}F2o<+7?rrVMa|IaSG$mJ zSpr2|zBrS7ZLDC(TWGGvg&iVn8?9S3^Tq|xd6xP_Lk0gREMa6(hw6!l-aGdvtvkG= z%B-b)?;W!Aa)wXNI0HL|_8{_o!h-B~?CVcm$b+_zzOGHc#+hsmH@OE=J^fA6m3IzgB{ihT1juYE^5C*<5I(LnfPHc~IcqbaQUm0O zQ(X2_8=TJfiR}qWW3&M2Y0*-m!VzC=mZ-C30laSlg& z*gM%15S&3&_QEk&K}t=3!>w5%yFFr2cGHZ^ADbV2KloFbP)E)WXFk#{xVQq<&U59Q zx|A8!LXu5}wJ?2S&{4AL$8D*;D0fD^O!UK+Z#MZf#SFCYncgX~bSK3<$_&V9=aF9G zTAj~2I{StKWmo;ylg9grKQQxvIUr$!_7$>b6R}2m5n`+e6+~64!o8N_rRcl?xSyB< zP4w0Fvf=djlxFgoIimag*w}ERwY8TdVedL5S@{8hBy(Dj!3yJhHTy?6H3wz_xM48u?ZF{ntz12XT-59|`H{*$|O(9Z+8wps1Lw$V=g zE)o#Kw;NT)a^_ts2*WNUWX1ppG61^r`87DH<2A*=uUu*^gUXKYIUfcw3?gK^ zknE0m=be5Q0(u)?lp1)OIFa!|d&x5?jyR=s_+cLsn{Gb1^x;`%Mu!&z==@Vz9?&Yb z9|~?_ZpShm4135!&GA+0fmaUgZcR_}Hpe!E)eaZa;~ChG^9uIJF7~C9ti-M)JYfpJ zRBCmY&>1^?i=Wq7q+VgqkdNqMcqNcePhk0$Gu48a$h5Q!fjT@1ua0M?Q+6^b+i{B| znUnt_vBr6N5vTTt3Db5~oqfX-ILj@+9eKeCwB!Kw-HDg%b54PV4GazMn?3*$D)9z? zId09sbr+qbP_xKZGr#2lk)L=?jfwE7Cf}b4__2{>hRLFuuR9$3s+;dAdj zeEYMUtQGnzmGz*df_i;gYc)(sc=KGcfkkA0{8(X}&j*SK`Xut1-oA%;Mw%yQ@C6uw zZB$@=38@}$|E;STOfEjJ8%@KS)6!#3dcLNcJU&i}QuMMPSE$nh^RAZ?f9Yb(wX-vJ zPn&~ZTlQN9ss_oM(?#8*u-oTC1kd@a;b%UX+{V7eUQr&WEqT9;#)@Y5CI8oA+X^Le z19{zhS%+TC(HR+f_h9rB{Z6@j6P?D57iDJ&Y=0IiP0E=|px{WLunl!t66|H*G&)NN z4$Wwegub-w5-v`T&g+M`FTM4aHsfr&@cbk0XUh3TM>W2?>7*&AX0&6e=kOG_mx1kn zW-D#bo6~`l4P2h!44Y$-U>ONFY`KXaf`b#buVi%o%NTljvk&X^%=OqO-Ffe(8$}gH z%!dmTrf>7%88q%h*M&6$TqdhDzDuV&lo2o8y>4Rp(cMjZ8?|(dy;T_nF6DLD$b(W5ec|=8Rgl^fF4BBVHf~(I~`-^M{$kC@rIyJlL!*j?_myXVccwF2J zEkJvogfrX$%2l!}2^sA#NXhbeWB3nA5AvoD5o{+rAOvu7*O3H$cWv_r@7Cn9Xjt-| z<%2M~*a&u7d|w|3&UnyzKekv`yGUB4Z84;9)j=O?#iK-aWGcHf=-ZF5MX(=Uo*Z8C zvCEpwOY?5eQ$1to_3uBK!|Qm6Aj&GqH_~uJ>uz^Q0Oz9hs}o+Y>}j9k7Jl4&t@|ID zk2@}0p!XGVDS5Jhej@}2F9>~LEANlh*F+TW3oOT1hrH}bMu4(zFw)$eO z(cPb2*$liE>OM|V#aeJ~iK$%0D{Hz9;TzQOXdlLkD2)Lf@}Fd2{6@j}1E* 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

>Jv_H%;zbbbyU_zR?x=h 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 PxB#4#l~qbvn)LqwYe)-* literal 0 HcmV?d00001 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000000..84ffa0cd3b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +exclude_patterns = ['/tests'] \ No newline at end of file diff --git a/docs/images/ContractDesign.png b/docs/images/ContractDesign.png new file mode 100644 index 0000000000000000000000000000000000000000..be6a566b58b43083e01dbbeee49f6ac1d7f6a753 GIT binary patch literal 816382 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

o5{+xWR0)|v^bK2u-hdP=q` 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

y92!}blpz`8MvKBG>4#de&!@dctbN6@ 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

#O~_YNd5r{H;&yq8sGA94v-zayqg1aT?PK$1>AD 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%0DMz5>3ENR1QbxV0li)X!CaEmJCArd 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�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%0AG_d{n#X0xk0Pwi-%LKLF*6$%4Vnd& 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/images/PermissionsModel.png b/docs/images/PermissionsModel.png new file mode 100644 index 0000000000000000000000000000000000000000..935b10cb7ec6b032dde999ce0a0c193595c3ff5e GIT binary patch literal 348517 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%0Aob(x9Bhdm?TZ=ueQ5VB*t%PJ9NiHK>|u^6H(R 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%0D0>En*euaFvkA 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 YPh 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 z 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 MXzxqeV{z&K031%b*8l(j literal 0 HcmV?d00001 diff --git a/docs/images/logo-48x48.png b/docs/images/logo-48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0dad1843806698354490052855144c50a7fb1e GIT binary patch literal 1717 zcmV;m21@yfP)MQ+TC>?r zGUIpVp8GSii5im!ALhd0-aB)5_xtWS=R5bV5LwGw*0PqhtYz(g+?qwCP0d{qI_PsW zcdd6W_q^_4vk0%ECp%<+A3iTc$(Kg0VaC1Ap}hSYs#k&PfquwoV&>B`@)vF_FNNW7V$)0hf0i^E%iT*tI-oPlRgSTI_t#A_>fs@i>63 zr!|kPnO9(W$Gtj?w}DiH@qo9gVWf6;H>?D!z@`DTLy>Sb(QqBDX^D3|n!|XG36wD3 zKx?{aZNNJp_zwecfcYHyiUYz5%e&fiYES(IQFsApi?6-kTe;ZA-})+N`y zsMh6v7*spo1i)qS>qrEk3*!86Bz`$U;~hGjR$=$^+;_K0AQg`7O1qpt&F>3Vq7#`H zV3V*Z?EFhEe^2bGl9y+IUE=a60iK10ATuj|KN_Kbp5GT%97+~}HU>QF%GfUNlk+E1 z>+H``zSPwSLf|U`yTxxu6@hu!jQC@W2=s9h7giyJLM4|3=4=N*x57F&u3?)@9_U0) zD-=8u8WLK8{B^L?qC6gn^Djr@>KKu^0BcMn`M=S5*Ze?^c9TtkSFzm)AYe7P1@do#jle#Iy#;c2CE{P!i#(f=Sml|&)A^JF<(3LDmB6kX zi$sC5mBfH?G^oXz0bl!@R5$GJvl36~`5hQP4r|BG)}@pvXAyE#+%KhEt|DOXSS_Hu z229eb3Y+l|<-uN9FKi3vQ1bh%5ZOZ#NovChdzQi`MM8y#!=J4O(F1sP0^=T7!7h9? z-~+D}Ajj~mAN>s)Y$@R>1->5kNvuqlAhpxOlp~L7vVm`s6#Yj0=wwkAAd<>iPSi05 zl&|$xM4$Pb))h@+ziT!oR8>_08Ww*Zi(=iUzT|%kYe-dPp+hV12E@!bt*ZcRg$c}H z&CU8V&1NUJVq7H;u+Go7N-3}6Azs&N66 zs?*P4@`*f=Q>@Az(iGMqquUU!N*~0P9 zIq(zgw$vJ7s%p5Giy3*X+G36u+qAp}I-VP2_y0^g7@vDW_P{=!%qCBA+;77BGvdmt zQB0PO8G(O*iAptcQ*n*{9y%W2c`y0~8@#RYRz4hQ5H06h|3ARL>)*JDpj2L-F*ODM1Nm%Iad-YWJ1HK1!L8TIR3p_Pte&Zi0gr) z?+PjK`BY^U>Fc6h#MC-TCQ{*&=QYU+Z(k4Pm_VnG1$k?nyjt3X?8+@GdE(IKxE7Jr z{WfPkUsH3xO&v-8u_e4>9Tx(BZNfXITBk#(kdAYzq+i3nf%VxWTwUm*Ye+Wn>^qSC z63;vB+t{)QnB8Ty@pr*K!?W*U$6?o-3T=DP>%sghE`ZO*p@kps@8lygnY@5U113y# z9=y@TGFj9z3+cD+0?Pxhm1(EA@QRd3()rorB$Xd7QWGAFt2fE;ITXaoYoua>g8%kV z7x1dNfQRcX4>j@qc7fN;a>4pYoqLw$fxt5a0{_ie|c}oxtvoV7%6nQFZdX5#x90?-W=5$J1vLJxZw7GQvoK?8Z)0 z0#Cs{fZfWVa|y*N`Ojfp3C|}nejVfb?7!2jWi4x2%Uag5wu1H-D4S>R>|YIC00000 LNkvXXu0mjf?Hfd2 literal 0 HcmV?d00001 diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9b3b19076800e6169af190a01dcba9ac24fbec7e GIT binary patch literal 206848 zcmZ@=2{=?=*grFaAqC{kwY>_2J5|wF1wrttCl`JL6mTZkgn^2+b)1Ovh zQX&e;GEz|{TbAz(#+|GCySUR^P?j2FskgS3I${izJ7)7;QLY6!`-TdMhYL-Tu$@7_C~ zj?=$ojwPuV27WuV_h1VKuC{O?vBL7Jl&0^AC$^N;Cw$sCz1h8vm-Qw5zukbu2Q2!T z#B27t`oFIQbx$X>oF( z%(M@_RNdE|#W7IuY=CWx{sL3P#tZZK{XFj)BX#)D!AKVI1{;{dxnFZy^2bO+pJhwe9uC~irUimo`p-y#Ujb4( z38*AxK0OBd2~5lBG>%0zv7yXJWCxKn8Ibq_i#Pqv`i*oQE1=?KVd!$}n5qs-3^g3v79Gj6Tvi}a;fn?^NJj=Lh(x=8| z>cWHnVE)6?4$Js^TJJ^3+M!;TiyW*~0cib)m&lK87G8>^U4$*$Cr4zw>p0m4=8284 z>D!uW5v z-wj>IMIu#cs%T&D?+9LLHmdlS-pzM8$_BRWrFYQsr|Sg|^~gch|M|{8Q_P!v?t^p7 z-h{5xBdqgG={Z{3didB)6eLjnkKcD4-w+>c;&m*x(96-pJwa4N2^~r;xbv+{jmcXTi!(XJxR_R-<=m7Yi3+OF^K$vifRy(ETy)%D_TS^gy+ zwj2F132!*)hX-|JG|as`mZRT*UF`wC+08g!J|fd1#h@)Ih<}q{mdCc~E@#MwU7>7q z5#5;h1F{;M1e|i5OBLA6hRAMOLzVbq@P1FKPp`{ zJz%>Y9ja^qfm*@lM|{m&U#)+Y(nUS4Nm~^yi^6l}SsC^-G>jRrw zjDs)(%d%NmUwNljMh~szjLZ-RTY5xv+8-Q5<&%Mc0sTz1hRLRNSFO?8fMEf}-Dfjc zh}s6?c%PNyr=h$ddmUnls4U297(Mgfjcwj<4AG)q_1*$&M@bLkgcT?~XIA8y?p7+Pt zrfyiq9<$0U!o_XnRoF5(<)4ZJ*&vvOSKA)LmUQT%>;|XYgNv9i%Vwj$rXf3Z=r?$Y zfn?rP;py5gb~SE)D=Sezb*GqGnDVVAi-_-x33Z=D?~~Ggw6b{`Z4E@Q`FUTLT|QbGhkbOw02mDEakX+yjlDwd$wYSrnXIuZD7#>!K~^M z8OnKqx)JAN8f==iTl*fgXBN;47tO}}(}dcqe1445sN$>Iv#k37AZi~Uu;n#MPAd0w zF}F4iv%}=X3bwpj6#WldZ1$N5xZ&wh2{Qf3?4$nWl+h!Z9cnnAH)%4rlGz-%Xgl-| zLsSRg)?|?0*~U%VO)!x&>07~;$6Ws@O=xoF6%*N)fLKc{odBVcL1h#wZIfdocaedh zTh&883jTD$fq#aXvu9kANX*Xg&=xf$bF0~=M`oxrvi(0sQ+u-h?FmFj!^G<(TiB(l zL6k+b&{>7%{V>7++R@pan;w(V`{aLl>$X~c!WBnq{G8PPxzdm=>ZCn9+Jw%cCc~T} zqN5D{n&hu%t1Me(*hx;dPgNMnAHd-}6MO!9$i`yxf9m3rFEqcW+Q|?kiu$m{C^7FO#aEA!`1+cv(_KwZaYEFqguc>D+?YlOyms{Y2IlF_k-rOIp zjN|)wPv%w!sv4kb?uEc9qtXj`UYK;GsZu4x<6{y&t!8uBLIHMrQRPHsuEDJ2hExad z+@!+hd}OiHW^Bp46}iT(P2Cq8e<1izIl2y5$J}o$dV{K&As=w`KhVi2kQwm^S&Dws zrST5~d^Ht3dkphLT4Pn)s1kL`nNXRuoB?(wEy+1PSuC%bWNb1usK zm0L0_C2t~U#OAUU2GF}PY+>r&skoktCfK}M^Y{n5+3q*~AtbP*BbT-+6x=e)9Ire+ zX42;{{quTX?^giaPC4^}b>1mGpB^m-PUlXlh=qKOPanz%(e1sn`N!YDq3`yNU94SD zdEs``Bv>D{8jq5yqK9H$531B|Ui+46{?i;mn}a6-?G9F z9wc#ryaUB4Iz~NL8%rb_%Oz$5{dg0q*EzQBU(zz`TXk?K<6zhyKbCN?3(ZxCM^MbL zR)n9NJ>Q#xrkXt!j#WZ;uU{0wf{nCb-3J~BSU0xe)tv`e(70O(o=?4hp3B)M5gHm) zk@6>Spd#qdP=-c7D?B1>*?T6Y?y{-Vj7qJyDy>w@=v%+(mC3;Z9bV8C25CFeJcKP= zJG)-@ItFOnu``o6I`TD#9^A5VZ%Fhm)0Krw-m}0}wnd$YuindNMn$W7=-{xvdKmBf zi|=sYvXSi1$1YO9W#rBjA1@dlqfgzO`sVmhqWo|Bs>fRwI|r5Wi`*o2DnGsT{#P>F(`!6&(5JH3j&FYbid@^sLN_CRsMnIEpJ`!lz@c3Y1n~q;y|EE74 z*LZDNZbuhFBWjsbdQTBYfPh=!W}Wqh10Kv~1O1F#;`lQujM}$+tuPD{dvC?N7j=8n zTB<@$KfmSG5VDuufRsOI;Gkb%b|dclcEH9(bkkhpf_}TtWg@y4?cNHJ-F)mJt zecgNavg7B5((<8vg8@gVnzuWPXLcrwE{Bw?R^kkhyP;J%x-V}>qBzj}D*0;VAhz{F z`EOA!V3%LpFVR@)@Nr*3^kUb7wiC5{QT?wb)8J75RNKbmJfoikm>3hZTe=ThRSDo%4(ziE13FX118d972RZYw|f62Qvxsy`Qf~ zgn?7^NfqCQdaIeT;Np&L-V| z@ElibS?BA`t3Yc!bahz`C~McG=@2|3l0fT| zzD-#hp9|2I`1O|Z0ax4N6wUrVS#j~k(!t6{_6e-Qm7KvQCVY4N$g;kV>Yg?|2c!QS z5K#cGW+TO8wNEg1UmyQ=DZ!bugkst<=`G%OyvG~~THsPWYuVi>`y?x=TBHWv!K zkR^p3D|4mk^E9b%%n}E-mDhM9V1IEo{=j~IkY`hzVzCqH&`?ifSXMnQzdhFAZq6!$ zk*jEAA}3jxrZ{$5e|+fITd4r!!9A6=4*UG-1u4Edve#^RL7ov4pj;&#)s+Pwf*e~G zt4pdk+LapKW1h&Us0cT4PLneZyLQZdGZ>aK5C2t6#1?(1PT)?&QhB*)?~c?z(_eXZ zv4y4;iMn{BENR!(;I%|WxE23Qdle(Z#%047&0;AqzNPudxAe3P#G5R>bzNm!`CfPc zebe?pFz##KI)1+tQ`Dhb_iPQI65x8C=vq^%r~`*4D-%L~XH{G^T#e~^ky`lqiUyoE z)Z48Dy0+bYR#fUb*rcJZExFj2$^?BCrJ+ATB6wt0#nkJF49NIpA8;B6458KH z56Ee#3I_f>$kSBrzsRC$)b_)PCedCJ+P_rj_ac|wEGs+=#%mN_g~ebgR_^gJ_xZpK zH%%gu*Dl?Avud@v<7XHxF79ImGL(#6VWNrLW6=+k$$lmc2?qiMf%2*PmKt?^2`mXe zz3$bgZXkRY`dE_p&&?iKuDnwJKQ~~AW-Qh$!fGg_tFxD_b~{uqhYcG~>`{gF3Q)GJ zZ=r_N;pypqy^gDZaQaCV?Ty61{!hcV()y~W-$N?(v6NCaLt~Jre5uf?^B$9xQ>#j6 zKga_T0oI!(wunWb!pa5=m27=+;AZ+4u{KSu0f^AH#jGftBF4)ih;SZkc9?X))_`B7 z#ior{)d1Bpyt12LqVIlT|)gvJL zr_x>;M!&=84(U&fc)OecjMm%1_eUzFNPe*3dY}s?q3fqz+_z!+JGT0JZt0WT9oy1G zSPdr9EXo|dy1C9qWz8e)#EYUEE-ghePacW&P+CbDnHgB71SW6WnGs%Vj9uZZxi|$g z{z2h23&D|8{iAi`!oR6xRh27^QjFmvGi56=NFbHSTDQIVZ9(_<;9`ABy2MFwLmBIub ztv+CZ2N^ajb!;5#HT)pVGpv%7;>d)VRqs;5QY~MaA>wM^vJT9ga{dl{T0f1&fyzzh zUhX--XLoUmc%rvECa^WH6Gmy?%`8jP@rxWpW;8acd_P!z{{DCzq+sPQ98JZ%=hwv? z4CD2|fGQm67Tl6&G;Ldd#E&N?UI&IUKBwxk0DkYDZ z4v2ZX9tM?fqsisnSto%bsgZSKMonI*J zskA>X+rmGTnRuo5e7FFd9>CPGo||$N9>c_e-907;tu~mBuXeiLm;Sd1aW+^r>e4@-F#tAwXK9)+#Om5AEi74IO_4e=Grvm$L=8cGZ5 za#;2PK4?KkQom%{I+--RU0#)V|FX%3JYFQ(>+lj%bwC4-rrCU?E#2EeUgLonJsj%d zR;tA!3lhuho2*-a3USwP(~~&OmGLKQN~7wr9 zuLM+%K#z3%z-qhDETBi)unHI2A#)jbo3qWNuu4;}lhwd=yWqJg*I{sOIPTc7X5iYk zCmiN;Mtfx6T&?`rkp;kL>jRcoBOgxQct7+kfV|Wq{*B=pOzYXB`gq}(zTm@0J_FZ1 z#VMJ|bp`3(`m>cMH)PyiVb%YMwHYO}c}TN(B;)aei>tRVnjy2wej5;xG3HZVpZOP7 zJ;f=9w)RInb#5s2xcBV6hWf&Ek|{h`wX;tDq;gf_q~NwQPXiS^UzR=wK%{Hd3`)3O zOUJZ^GYdfenh)vT5-l(Z=u5B|Pc1=XWzh=-6TzOXMS{W=a!uZcHsk`t*IBPt!h9w9 z)Y^4;@CC-zUb&U)QUR$$gg0Bo7>0Lr2Z-%V;-L@K1;4W zTynUVV3V@g_D9lNHCJ_U=ck{^z`VBrQo~9a9U&qTBrS$*flBDksnqK(N`5@DR!Nk6ks zAsgQnCq%>*F(-9sD`q|J=bCZ1G71~{ZpDH>_Qu~ogI7&hT)4SeqAoyg6DXlecYpg+ zfa$WD+HnL?3)yExpz8QCSF3n&y?VxT;LF|x35c}SA{*mky~MkuXJWWzwZZqgTx2}V zT#&Lq1A>o->f#j_`<+`K&Z?YZ36Mz~0TRCA#C29U@~&hq9-8VH0Nbqe2*Ljas53?v znX=>N;(?=U9L~9(6C;j;Y+1b{T|)+2nsvS|@vY0Jf3pAvl? zfP`2<@s5fTTwO9!x%Im+2mwwIMbHU2D+4bBz9H7T0IZJ2DStRwe>Zco!VSFSCp-ua zUl!PfBrdkQMcM3Hv!?+knhJ&LoOC_{o)CclMhGks8hQcDtxF9T5s2I@R+p7D0wPNk zv#XVlgAQkR%egt1-RbHev)qw6(C=(I?cww!X4=R(Oo>+aTlPb9h-u_27$ILYhh&HJOyKa zFa<7Lf3p{jN}hFxcH(h+P4CBouH|)Cb-5_Je{zCDj~@1OEo)1B+rIdC@B0~)>(gzC zBk5_1ajkaHWCbaeQ!3!n?;$RbIS>*0sDI}7cV6VH-MU4DS~J+DmhRCb96Pd%_?W1& zoEwBeU65N-X&9u_M?cK1k^fkCfwrGIW2S)>I1A7HY$<=5tcog&`x*7p-Oq*3@MUOT z2mVJ+2!p9honN+vUE9j}oOlMkIDgjnE^H2L41Ygm$jH@2~#IA2e0;=!jcA=u_x=#o;BW9j6 zs(B)lkD|uEH3Cb?AXYB|A)wwktYX!>g7jqt(Nh|}U87VhK;=zt)WuRxe5N%@9g#go zAzyT-|0rc00Xnv$j{hQ0`pA(Jq%Rw!yUn7Q5V#FQ%;;Xe4{^yU{|6U-)EXT_a#9xc zytixDN}c$R!|C~f_n_iGF$`@*kUK^09{W-yY)a}l5N>fN zVSFUFvaIT;DDS7(!k>eJm^A^(GMuor}ilnMNQtHH5o^hx5kCI3^ z7UfxfRjl1r=%Kr7SCeH3dSlo6t|B`A$9cQsKrL|1iV~-Uj(#H5ZzyLbhXu3yvw7E# zeCZ<({>X#ZkN0RX3wq02?sN~SZtDtCugz!LuJf!W!SFU18WygCv>mlA`>Hu_zc{u& z8&5eTE{Y+iO(YKi1zUASUDT7rH8i9?^REq!HK7_!A;S;vJX}Iixfu0inI@?i(yy@I zr_K)u-4zLhfNuH6cA0E|J{*kN8t_X537lRh)%TlO5ZwN6t(4pij@90u`75?IW+am=AexBOY1Ifcf5`*`l>3G z-^*{AVRZ#t_-yJ=0Z@QmuvzvE=_|AWie~Cclq{L0(NV7+?ZUME-b<8)I+7mPz(sx) zGdH&Y1|7wU zzozj|pJ)t`-hj;dwXuOfQ$=kxTcVh?RfQMn489a~?Fta`*q{eGy7Iy8n!X3k(d#jx z-p%YY*P}Cu5I$8rvhx0+$)(5*vuB)Ck;D}^p?r}^XxodTn9OUfi}6|sHSki;qg?N>N5jDbTv&XY@9sOum zn1dn+h0P~}pRguT6c@2WlHFd*hh`R;zyZ!s4ab2JPag5X(i!^X*-XztKLl|D8{+vm+xJY+83@>X05CZDA z?8B4dc@P-P(hjCDgDc4n`VI_2I>+C#qszC5F6p9L@3LF*`0>QSsFc;rQ$1rEI2dN4eR|Ml@0Y2ldPP=lHqv`BqT~NX`BjLg<2;zMnEoyW`*qes{4Gj-lTK;WmjYwzww-+RB)goFAkWR(OKBdObY97|m> z*iwy^Tmzx`YR`WBu+~Sh$u`%hEj@;bw~S*Fu`nB^rnF7Ryg7{jLsgGJRX2LQ1~h3% zNN_OK=^Dv@BOrNgge`L-Yhh_TuNgQAi=WMRurj`fr@+TjK^|Z~H+S)~nfLlH&IQ^^GRo_?8d9>g(2#-XpV z;AP~)qsUb709(DMkc=9I>s{2=1Q}AY&}~f5*WD{XMc0>!9YEAxo$>D1Ze8%(TUKyE zJ&H9-t<|nO>GJU$FXN>Gk>IH~PO$NmF6enGz$xj_;KUkl%QpUi2~pBsp7=nBKw2SQ z3#hz5Gs8GYSP1h(DlEd~Sluy-0XnIv9u^=M!2a^3a8hU#23ZODVGIky-tiO(FoA~m z3ut&(_?*OndFUrjv{J#y+;S2|u0D|fO;+xFan&1PW!S^=lSCubIAxo8utHA*_~SBf z90rHFLxFK)2M2|(!G*Pvt*B1%Zm-2HWVkKEd7=CeV~2Fok4-5+|oEan&xkmJp{l7*G`D>QWrT zlI+BP;1n)DV%7T!7f}0K?-U2Gy;l=Amttgm9jAcSmqWm~E;|JDYQTfl23A2u5kfIo zW-i@*u?Jk}SwlpMLQ{0Xt|u$OGB|p~w)4L!4D&t$gMM46&VF_Jx ztmK`XUpS$mPmd|!pUyE4R|Y&bU1br*5{tqW zU1iKqak}sUH0{yiBf(>H6IAMCBfn(@FOd~4WYOH1XcVAAnh;`un>qf%$SDaRa~zJ< zab&&5WXXYwc23j;i9@PVxn3N`C)^m!^L0EVphBk!tOYe#!b{Phl4!J@KoU#u2Ln^e zK}g@UYX|_*N}Rxwb1`Q~2BGN0zmYy7r2-=$9b??TM(St11jSig7Gr!v!(F5Dd5B_Lgins3`RpOW}mHCUrBcu-A*J}{s z8t#9@sXi-(+D+q0pVLQJ1Cmj9E=@D4y^{#;mI!SmZF0%RJ3S9E_!KSDH4XtXtc`w) zv#wvvwajSDg28X62>8~Yhd1tbUoXc+34OeY%lP?E93`}2afm63)Teq`4$I2(QJDBW zW_+}W;&elSIVai)lNQ|fTIq{&IO83^%j>D+eSc6ZyjM#?mHS+ymIp}6fxfYI2m7; zDbPH*is;wPX?)@Y1JtKLdo5?hyEsAAUJc1j7^e$(W6%^373I-Ku*^xa#l$}x0!|mb zSz+iU8d4=4FRTV_-JkHrgCC|acbQ~$IXnZ*f6>$yl#g4E3eV3as$3BuS)J(SGTylx zQa{wFHsb)Mi5}6B9Rz-$r;LZ_q2y%}45Op#j|(tUNw6S>wAZGa!+7T^6;jYtEGu2J zYqW1C)F?ZhU$WAE6|jC*DiD_peK^SCIP3XO584-vTX(z`{Lr|fLGrp65Q6O`_u0a|X(Cro_#atvuXGwhik2Q-;}UDlTQ*|vNl9^(`b zx4^L31Qa^5VGXf%jzT>Za zVS{PweadQ4ONyY37FArs<#aV(4bjT$0hX%$TS>^ong2J+wvHgFu}uIuUCJUq;>yOMqQoVnM=&h*eAysX^$JTdRzHG!c~%ThDz`Ys z@_-(+B?Ev4GbCP4S`TyP6xU1G=3%zthFEnUc!(aTUcTM47MNSX;gRi8Pp)#3cF)8_ z;jdthJq^WC++at0{*vTf+$s#(mmrqGdB-r8Lj7v6GE@g`YHl#y%wHsN)Jz(CXgHc0 ztPCbBo%k$cnH%Mm6p_CHoZ2e>uw+9N7LNrz#xRSuGq?J6gVanSoUF z>VUKYT>viGo>vl#Cy9$ociXH0ABSIxgStL>mNLiCVm(eIhYu*Y;9k~gN>$ZyNV=#K ze@RNQ2Eqe^;v!=J=sB^LhXTkiL9hOa>2of>|vCsK_C-tOUp>GZ}1LPCN7nev(bq zj@k@k@Nn~T*0XPDViYtuwc>@o!KV}Er9O4Xc3uPi|vJ%_1199are!M z)7k#u;jp&Uha2z+=sHo!f-Ei_4eILN(V$BQy!=pOYeXOgwy?d<4ocAI3`nuLdoV9Q z`g%i$CIyxoudaB=ABx3FnR9W@>l`Bl8Pt zXheuBj`jypQ8{boMFMaKO`q_V&bPy`A=ovZUoXlU0DwR9I5vNg+;f*<03zjjfi7Xl zdCIy|`iNT(I+7a1=4;+-i9pK?YZ)H-06qr#TB^pX%HjB~O17=UWtUom3x$dXRV$ZM zcSF;=WV6|_1I^fQV&O%Xcet%f`jyZ{hu5%q6hIEW+WVI9(hY<&n#MYAG{rWiz`-3zyE$#VP{cDJ1O6(y}%>UM3EMgY6LOS`64fLPHMqQWV z9bOY+kar2%;4Vr@WAtnd=blnn35U6leU%mmiH+yX7Q_L-9&XaM%{kBQ=a-|#Z*9eN z93Sz2ac(!p`e^_w2`)ymDT$!5qwk+L|G|vAu2$y%9&Sy}!PYGo-bcPo_T=1Xy|m;k ziSx`7skKqo+sP%EJ7=f+Cd(@}{;CGaHh=pAPN<$X|k{z?a;07~D`=SHL0+wF8wpX(WjQV+jR-x;4YFcBcnsCM!1Td_0JW z^>tswD{%Yb8*?;uY#Kz2WBU>!l&#KRRSF3zC&%3Yjbk>@lnh2u*N z@LzOv_8sncT!vM4zIbOPAWJpKz zvp}4;E&sq3W4t=w^G+5>IG!OwF5^4E3S)u#C}Z}CWMEQcFxuc<@*#X*2LVW*5+S}= z&7!aW@KI$-XK+gcl$-DD1h~WqCsE!^4$1O;+=$8lGlxftD@Cp)42T~vs@>5HxK3>F zS%&P$^9E)KW2{9;9^8vQIC(N_Ge~&UdD52o}rxEcli6E&yd$gLLwrRAx5!FFr@UDVG1>2=4mt~C zqe_(nDUo})ryo2A!&}_;?;ivy;`*$hjT|DPuJv(`ACNO75kTjohQp0%L%{^n)uvN1 zR?G2S4#K${%1Sz&EN}u1Q9CbHjwZol|Z%P$;OYGb8Caf!}U0_rcL0YBVR(}MB|Dwobsyt37{1Tx#18_ z!wnz!pap$sc(J|Y$3~>bO=}R7r8FGXJEtX9dM{41h^R-P^4gAo6oHoD1$8L@%%CmI zFugg(E58=+LxLJ@Y)Wo!qwYms6{6847FBA9Lk+}q!ZY~5X0XvmF-E-gLFCdv7a>rQ z*s>JiajJWbxcy&i$robCQ{fLV!`b0)aph9s;)vU@CRRCgoi-~VOd6vI`}t`w_>o_m zd^iv{i>pe$WBW8;e;kPM2LI4|OTGI}lcH2>>O%no@oL(a$K^ z_71+vID#F{ZhL4~wu4hx8`5|FB5g!-Lj>s*j_RMYv?ON+TWDx{m_a23a%VZLlL7X} zpu2y{TYR!qDYFDm!sLpd?GMY|Ij}&ujG9QlP)L$T z*$(oyH7QbgGHD5Jmya|=LafZ@0P^-Big)rN-ed)itso|0a2qI>i4pg|doOD1GHBKO zqT|QA)>8fVFFunBG+jhbSqLi%o;xPs&SJ<{KWN0v*d(3txGmj1KDY}*wSiNFZ@Gl9 z;@9L(_IZKe`HNyPBws81t;3zI!`Bj`woZ571BD_V^q|D%U=RXT4e(Pw#*ygJ{Dd)lrV(<*)D z8Tjg%eU(L#PBw}hQ=(zWs^R$<8h$|gxmKhV?(3+ERO}yNL|i$YK-Oa82%6V~SFh0q zHIF^D@su4OG}0%tbEyhBL5&cYtV+PxZf^rqPfX-u0YVvLBa8R_qu^Y>od8K9C+?er zx6>A70cd?80Ui=zzX(RSYOlSG-z4i?!^l7>xG@O`}i{XoJRAjOCDOyL56^A$#zc;-2% zdGK1_faNn>Xmz4|Op*S@2#?|VqL&7QvxFi-RuHRml{NVzGE9c13H<6<^394W3}57z zH4C$;U2NR2FeZ>@KkumBa~+n43{CZmQ6o{UPO7fvWtW2`h&uv?aUjOR-ezFS&z+7K zc4llv)z2yyVsO)fR@!k;4bpo_llJ_P5;pyv@;Ew3!9INsru`BkBU!4mgT@!$gkhv9 zZvnYI8>IS(zjR=@5zkcO_Zr_KWg)HiQW_-ClAHgllqLc0GV}E;-rMR|1Mm*uspII4bCyIa@?_u=g)mtfhxsHBXOhJ(ld5uW~-^g zI#PWB$~Z7=Y;VJhy9feb#Ifd+X>96CabqdE@a?Gk5@7hCBX;0gl+f*<_^^(GEn=W* z@O#iMEbY!;Eg4p!^v~A4Rua`Um(Y0i-hnRX57>cK4_;sxS6B0cU0y#jmr~Txpm~lh zXr}RsakiGXYl(6If6X(Dm}n3P{Ig2(J{;1;1RhR^Jh#Y^RMbYUD~J!8_9B&-E1{LL6XR|HX`Dn zBwOiTdDN3V7D(mq0O^Glm6xDwRw)x|;x1HognvoMzPRCwpJLZv87Z6Brk8IbpWu6z zm!n)RVUYWqK5>%6@_$>hphcdpYb$9sWbq^>R=V3SgOmO~MBo)CIPH?zITCE?I7&`9 z=lKrdcp3u=^$o2yN_Agajq~}r5>?%H>Sn@%465Y8X)# zZY|zxklyoD|CSydLlOL72g>}`g|n(AVL>)o_och_{4kmc5n%_`sso>I8zK|L`;cLg zUdOs7?T)<$Q>%%Of~z0f90PPCdmCr)_P!U+=k-$7mdMs@Y&m^Je+0B@*XROr^&Zmr zZiA_1M3_nnB&LwP;_h(~qM|T{s9?w*98x%+el-GS(J<%{hnjMqQyS-UYaj67Q+rG4pp&drH^UOF5+H)MR9}Zg_KN zFCNecilP&`ftW(UG_CQvt_?sq;+&}Q#>CJv@&EcI!~qounP)^iuXwu-$p~mqPQn~y zEkb84GTF7pc1BrO9NYimRMgDOZiVU3*_1&1J`BYkKT$lMREBf?Y+`U=?K;5Mq^~fo zH9MgF&i`{iZQ}fJ01(c-ap#?O`}H<_q2Ob;{q;$6@7cpOgryhkPHO49tk>^2q0)RR zJ2GzaS$5oH@5)Y2;9BQwFqNAM?0v6y!rsl-;ibmc#dQYlF+WN$OL zab`ZWBAJ2lwB1N^y=s9HtTP*?-obMXtwJd#% ze|poWd}pjAmw_0wx9?t%GNS$B@kC=JWpY>8 zYWMkagK=dcNRaIXyj!QVbi*4wTHFK#?6fqCut2NR(_kv!9oRp3J%oep2mgwhbtzR6 zKPB#qZ2UdE1(16#pJ?zqybe`o8w($9_-Nnak9PB z6#!Ch-?(FS>TBmDp>X*=xBX)4WP$6Bx9{=S?~66&iG_VpEBKZB=ksxU8y)ZY2G?ne zg$y?=dih-c`NyMmZUW+E4R~vSc$kiD3JV?C4>G&e9AINpoLjgpfA68egE~tx@nHO> z)3>^)ams3-drXiR>9+rV5)mBAb^2Di<;(r?7fYK;^7z)wpAbq|=YwJG$O*)F2_WBX=*51GFTsVWHM zPOlc!B!H>38+Qo5L(ctOi(K92QTNSk8m5Kcf8$;}9U1-ny+N)(aHE&So)u+Pa! zmDIUasgGDLC>8lvv>!}f-+SymY|JRa-+Sv2n{dOyX`(CELAk9taIwf!lm3iiyhkAI zO#6;72c5DVcEP={MQ;x|nbA0oIn;Ag>7Hp04hdVEI;lJ)Yw>hssA`;RvRh{ecc|#N z@BR4}A+?&-mU#E)l&uUWt$Ng3*+g#ulTvnWYW4WZZ|8x31Z=Zsk>L+EL&UAC-TXf6 z#voOSshS~uvysn%R-K!@jkhcgnS~2$4aw_lqMs~u|9z-y5ZZFbye+F({mR_luHEBP zB9;ADX2tcQxg#w>O1zH)qdXR;WwQFj7OS-o2xLYfHSo(w7~(iltaL9Y9;19b;C6G( z*CdIE-6q!u9r`Q-Yxd3sZ)Dd({HLnE@qYOYKi%ix>LomFXt33Xmuk&Dfbma#Jdw(5 zPRqy^H6p&PB`)O=Yju#DYJGVs^cx zK)g3bSOgvbJ5P6OGpR+YUJj%FhuJ&kz`?gYxp~TE7ogh0OfX86Fs$8X-W&aMB$H{d zpF;DY@;R#vQ(oVKg@0rlDRH*(vB`eU8zP? z5zJpg6^8Uq8C!a*vbI3=n#8H3QO?K5i!J952Eq~xNkG|bpzGy+15u5w|7GUXl(kIs zzddshJeTCRo1i3s>%mL+AOx3Yjf9KcOk8M{;BAkR%anIoJ5rW#*FJ+Wj;M7}7bYVsG&>em6C z{l&+(SS#;ur#ou36YHMHN zYP(*1yqGp$012;HK$Er$OJ5~;h5`8AZWkDs*guBwObQcMuAW2ifT7i`!q>V*OH*#c zgFEcslcU5rY3=%E4p0TSCP2=xBEYm)T+NKA_~97lHdRNW#;sbA=+gQYUFI;Ze5Y@N zQU13sQ)^wDH2vVI`VyXO%t6~s!p{GdG_vFZR3zP-VDjCRe~x_2rvULMg&&*%mt^IM zy{kFR43_j?TP4m63d#a=mKFcI6j7XLO>RCPi-YYW`YH_E2J@nMFBH7w*;sGtopYUL zujC}sy@ppJRiw74+z>~|gEI~~qkX*Z}n zY;WUoiHp*BAy+3V>fKb~%JV8^Cc2$&`#%qCUuf5_W5J*x|7TqbW-e zOs*B1nKt)BuGsY+cK(lPU0`(A>08eB3F^yAn1bvQF!V=ot(k<)7=~HxUV)pBSI$um zjDDA2C|E3VYNSoqFow^=C-i{`B zC0@ReTXiw2Gn}@0jf7M;wAyC#c{@+9g$IE7)4tMBI8J$acQlviqepr$@5VR5& zh*)Tk*PsoZ)2w;)xra1a+=QV?E7o>zX+nH~w#;xsE*FwEk_9r25&V1Ac*k;JUeHuP8^m~YQXBn0zT^F_W?NTy_ z&Cg;cW^x0n1^k$pGfS;N;4Pop}E1EkyCT%%fYGycJ-vXlR9v?(;dl401D4`I{7#9 zOTf<&a@P5>IcrhfS=9OgUq#y-S$tStZDMRtF91V7b568r#BMW!36vnV%>7|2<%5v) zhP4u*9LSlu5%?74q`D zF-Zk^O^4ksG1=F(`9;7FZZfyUc^aW)JWgrP ztcC7Shlp!>Cye4@4zlr@6Fjc65Lmhws~nd1YZ|M=hJwH-88*1hzF=Cf zLht?Um|viZ8O?RC`(`~czyIT`9Q5uD>=!qFM(=%K9)?_BRri^^MbT1eMjxJ z6HGmZt)YV%803&iyYi%tbil^BKA_vZeeX;#F4LW%jMI1rI)-m6-P`tKPHUup!lZKU zUtJCvMzs!l^ux+~hKH_W)iJFJLT6PP&+P`I1FyAJm}45Dn$Cypv8|680qqYUN~2kG zJ`zsTFQ|x(I{x2LBds^wWQnGb z;VZA1{yn@0b{f22N>FufE}dkutQk*n(dg0qArYE04x49g`&pSvp4DA)@DnDs2<)v< z$nz4eta$}|lqzSY^1qQ@G))hEn*ZZ#%;MYGfA+sB9`de^$FSZV2R@3OxLaV7u!L|f zZghtzH0iDRh+uFtb^=eCGFbn0$d@tRw%E6&j^Hw0%?FI3m->q;Lc*MP3{kWi`#Y@ z+W&zRj+S%d&X2Fiul+gGPnnreX-?AA<%LX``%_XPF;x4H9+l%h(>ij%=hd1ML9lREm|MRr@x+ucOWz4ef14_sj_)jV9>L{X_ZiBT>>1NglIzgQBe(@4|p) zqMmBLkZ3D*&0Kxpvo>TliP*zgJt?9k0#ee7DLwR|j#BDy`A&P-p zjeqYPJN%o&A6rO(!E}4?nU!7yUbq&uLu<)UQ9pK^2(3X+{A-(X^RnCigHCgWGq4(a z_ntWlj+4p3#e``XV~-X5-tzRz2~d`J$S>bo)E_gi(RWx~)m0c3zvbyV54TFY13?mt zaGt#H^v$$#&VJ-?P@!9g=29BSl@*hjIM{f`V{0@w-U86~1b&{NbW;U+=J2Md&)H8e zMSj?fsg|`s)?+{6+#hlo(oS_JA+~cWDVv|2e?KB+8QuiBwG_UZ)&lLcYt3A@4e1~n z5wsGRgxah6$I!s~u7E-9+8^a`*xY`Erf&|;i7~6awluISx~aHZ?*X9GJjKJY!%FMs zcg5Jv+|}vgw~}2B!9sfQF*U3%;QzILYx3`bl|O0soQRZEm|N$|4D$D;?eT;%KCO3q z1-Q77d{L21j^Jnd+|!eFx0quo5^!rdr0OXD99inVba)b-=)H;|t9x-*R@P8kKJBR8 zEuJ@@u@e@KjmQJo!QMS>epK)6@4|`2f=`6DqO)m5TbYFvtw}vU} zvoe7Xd^|g>_tSpPX;V11&+8O(WoaOJW%wB^ZA;|M!>Kw(wdXsSyioRmPEn0{S9BpN z8=l4(5(v`)L*ta^wKf`(pZ|mGV-0K+%Ibh6o6Pj#Y$WgBG-N_pO1O)4+rKj z7X~rm`gNpzp^Yt~fZpUR9)XQ%gg@PeG=|>^--EG^GUVWWTSD1Ff>{CJTOf!^fk_ee zu!Og=o|Ew0XraG!R5DzgX>%~T-I5T~xR?2`&B9|&(4&&P96k3Z0=JnGqqQavr{TlV?crgv$1mJAUMQZ8o{3u*Slm>U>;U zlh%u1#kK9FpoLj@%%sPiN$EZyC%j;Zl^RJ)QRaZJY64&To1%xpedjhD`?%nrptNDc z{lkNzUUBLuNS8q){aJVnEGc;d3$xnbyE5^SkAI4qDAe8*04cIyQ%~T$hTA{86E@lI z)<_2jJ76#3q`F6c*|rMxzIhgy&{yr)&Cjy$YVm7((k^<(KOv&&p%upbETT3_lS&c$wp!YwZI z5%-VQ{vXyzXYY~>$^pk0V%iKRKFm~l`?g2fm?wg**b$Do7}$d`XVnSGhGXMv6A5Eb z1W2)r2pp!+Hh<~}tsYGn5vN98=V6^UtxbNB{|{19JF{P*cGEg6Q;Vd5h5zICbv22I z&aZ;F(+4X%3^yYU4{xRw%^Xi$AlZ;4;$f|bXGPE?QbAy`eZE{bD&!k@7r8)nHd6F< zeEqwVzfFCRpf4siyM2j^Y1_tsMT)+4ts3q;u1ocm0e8Tw9-U?X8UVRvEnXlT?!taFq zx(jWOe}a|mZX{Rm$A?~E1ZlhKJjo$*mssu%L9<}H4T)#Fa7`Zn*h~Ki4Y)325bzgh<=Olrh?;Jh{VLeU` z+l+pnYx{57vS^=D?*BA?iZtByOscik?{uBvUjAaiCgrO*Cv!+U*bx}?LNR2)!*=zV z?8J=qNRj1Q-yw;B+pyUm8c+SJwZGza<8`pTYkktJX6dLbZ@T82P^|*i$g>Y5lHbp4 zxYPabuOe_1z?Y@S&o@`XWa4SeScH1El`Xa zNpL0leQ@17zgYd7_t2!`vH@_A05W(G0*jRMpf$Z&g~K_;gcZpx{3qv@fqux_MfBwp z3#qZ!1!xx(-nMq>Y&#}45-ln$0Q)H+E~b^uC)LSX=Cjp4shKh3NReps(Xg$PCW5O+j!B}gi^v}}X*zR`_k=_-l!jZ3tue^V zL|EXZ_u)$P-0t0*%rTXIpF|l5KJ3$8JR>#^j}yeE&fc zuBWg(DwCwXYDpLNrVHj_0MQBg>xIEDnr3?JX`J~v#QtI9mCMc<$iViXu%dgWv&1j@ z!_0M)!?Gfw+(}k?HHF@$vxH)pEx0z`=-D|#UbfSHhUPg&{&oFRo)%VhX>C}aSiK9j zHI;nRJo6RwpW#`Bc8Cpwu|vA#Tj~4FsOG_b{mM86#Qqdn?0-bb0M<7ExvLEBJ@&Kdb`4feb01nfBv@TIzD;6nXmn=s+f4= zprVXlTv%cvaIsr&kO6Cqiw)Oc=E4+gL4;1{ zNh)IBCHlbvSL||kMK(t z_|E{%zH!H!=(mlO`$g~UJI77<(bpy5?(yj?!jclW?ZTLWuOJY6q(h-ZUfX;Q-xfp% ziGUTDw6yLI$>@uqw=X%Qi9;$PeZ5foA4&rbvomKfKKiU^mYa$2%0?$FgEnBf#`L9h zcx6yY^zrKJg>j0HRQDkJIrg@@+i}Y&!crnlJM=w;y=A<0{KFki`Vy;WA~LzA$8>_P zZ#DBt(oS`y{)53{m6)rfCua;?MuHH&oP!-oCq}Zi1B%ZvtU<Yz4{iwceBdYY1CnTpxAE-AjQ5P)7Hn(#V@`GNA0z?%o>*ugqk`46QPEn+rVNpyNwvkS*=lH`eyIHcV2wnYWeMuq| zI_toy=&&D0*Dvl}f3ok!D%=j5>uF$NTUenUvh_=UvoEj8YLMAypUK==y*aIvXp4q=_tK#q5<4nw!F5W*Ceqd z6TWzhy}Y!E{*f#(lj$IW!LE{ZL1~F~6Q3kLU+Zb5HE#@2ku=wm99I*}R8wpk2 zy>hF5@g5I!<%n8*91?xjdG_t_jBlU5Y0a!jOquC|XD->X_P2tfy}eCgh4QBq|0b7B zD=9BQh5$yp{ei`GHk;x3=$lFToI9t9IiRr!akBM z8=InDf2tvtyzXa7j-l>Br6yV~YB6Zme|GXMh4#Ak_?7PVv!Six3%~SZJ!2hQxJ?Io zVA7Ls)MFA<6S~559(rTpwkDm?u`Ue?OBtit9oKLqH1KgTYvX_SsM%%MsWh40JQJDt zY=6*f$d{=U;S>KPKElN$w3A4gEdc_2OUO>=jn!D3&yQkXmRq6pq~85 zc0cD=9hmRJu)~{oE2+OGQX%TwOJCdF_xW`R8J$ONkg!sG#10fyUYWm;FW@qSjR7&D z%EMBw0y1lgO*HarH<|sdK=3<0cUvf6+1zvdhe-FNlHd34&>=8viCssKA^Gb;SFZd! zIy8XP^c&gkPCbOLby+L8qi3;Q`fEuCPiA@I5AgL*Fj5&G%;c+aw7QA6rB@XOFFYW> zBqzq!x8&ab-*D=rr-;%b0D8(t0NzXhHvAl!z4XJY+0DSG5*}xE?e)(4b>3lCeotHj#g>PUAG~{yPGVz^zj9!*NetH?o~Urg7ImCm7%*qgAJc0oZ)qkawc{I zGB1;y=>21+8f$RjzW(9-~Yu{pb0&5Nx0OEXFZ7X!#HQ zo5DE$lI}q=Q5~FPj8d-Rq};H(UkxC+ZRA6U5eKyEs-F-9!2_u}l~tm-9hM1{)SwzMBjn!!vgydRjxJ&l7PnXgW7;u&}X zoJ1`PFWw@{9Z=7hKiX+ZU#0c@n z92`Imd0q@nop(q`MWUL}pRuF_=whILXNCmL%UYDs47h$pEvr&srI?KH-P&;9rIDDo zPy!5_ntU9M_O%3LX>x9+YFf&okB|6GQERUq&w0up4-g0ilK8mvT9SMx;EdBQ9&w4- zDW6It0P}*7*>`gvQqq2&P}I!0ikjm!gJ%=UrTpl`2QwE!UB_zHE>Bub5jx(F(*e4Y zi@F@~9em!ES2?Tcx``W{>94SRnb-=6e%oY?_9M5$kVl8(arY9{v+C4)2Yaj zq#1PW8$a;3*vA<7-WiDPAFJg@gv!lR@%I5yBwv-3ejrZQ77Y^MW@qWV7UC6(TfY~7 zA<3P9qYy3UXbm*NbbhQUH!t!iQYr}D`%xL}%8~d>ZGDo{4^ejuspwV{TclW0>DI^P z!K-;x0TvgWaRJ0ldvC7*q2i_9Khv#Wx7SjAi?@#)3~p_|na^cmge-xZGAxHAF}SBF|u%@CRIi z0>$hy=OMTDlFT-kRM1+CPFd_(Asw_L=4MGwmHOAxh@EY{aJ`S-=`;?QaFXx zXdUdJ&sQaaG^X;d7BhyEW!Cs25vv|$?L#lFy52~_)Z>!u>dh~ZZyNPZL>nPr$ii(N z=Y{AuJ_+EI(z24P2(+3Du!alh`25FmM{9WRN4`Ns6ztD*HfN4FeN1=&7bjSXl-q$* zl;^J5IoKa$jO$+Je}KAX=Nwew(f(p?0G!OZ37`LY2Q1;)! zHidByWjPTx(?N(D>Mye620A!Og zYkPq_hd4eM{OO9@X|l+@yE)TL+1lpd2G*TxB%|k6%6V-`k=o#`h@%37@bdhJHRwe! z*S*E{Po$JYH|^05o@iMYLXbl~njtPF^=l}GHRQ;ACi#Pu&?4Ubt|?s@IQ+3*qb3y1 zPjZG3t`X{R+6*ZZL67O0&s^J!YHRio)0{hVK<*#v$L!rWI=l2EaP=a~l&ZG?e1>O%CK>?abvUQB4JY0tOKT|-NFEr2tv zsS3|x6$+lhpb(GBCbez2{GC3J#!X@cC=Q59nUEEec#2-Xxg2?YpO}{1obb(>MI!HI z95g%S!=9-$`X}Ju-+$2XFbD|O<7X-vq6=`lCS^hX>Qk>OL6B#5Q``y^!;mv6eqMNg zrM$V>9wvhN%{V=83RqO5X)xv9y80mO4O;xyc#>Jfb971wbpzG@?^JH4MI=T?uK&(0=U5@st3p?VGSAT)X1z8% zEO$I&mayvFMT}Y7SIbHnupQ6k|8kx49G$2#)iYa|7c6S)I~x2!>GY3@bp%}UQz#Z9 zcd2H*#`kRzGMtff+!f&UYp6tL@ti{E)nxD=a>TBL+^2g3e*vPmtl%R}0nX;cS)d&c zt-*bHt_30b@w}{-hLw0ORGQ#uPgdRJr(1_ilfu&!hB(u7kl^x6a=mo*ECMlo2EnDgPK|b?b0NMQrICS&ARNzZ^>x#Vk#vKa z&<@GqlXzcu5KTun%OW#v{~=ZsPz8+|L*-qtqV8PDlfNruCCx*RZxfo*o|v;L7?g@W zdNZDh8&yeSHASv5^Dku0UbE&PdQ(##?=?$CRDFCziB$qOc)ED!{>|IDzyN?&g z66M}LYoPE|!OF&Y4qXZu{||sq z!c@`6ce9Bma)bAe9b7~mGMiX{mKLdP!~tL*Hoy-=d4^nh7N04uP((abRm!6 zZIBQ39eRt13vnCC4H=!_J^DJ4!uAr5or0KKov;_(8?Xi1N$~%KTja!QH2a?5uOT{PaCJETw%3B3 z_QQ*k%aNyF4o`^zqz0Q8pLeHuX13y4$70Bd7G~(WhIQDgc4~eHoF8{a##4yXz^K?% z1Hdt9@=`s*xv=5>)HG^Y5BPe+I}qn9fkB#6Uk75+Xf>~?(J(7PpavPG zYG_y>HgIbA%P<3y@t3{zGz}XrA41qV{?xl`L9~;P0|aoSmD$A+Z*sU4UZ1;h5i?w3 z_7pQ~*=63O0A6x7NXcFjAvt6KQ?wo=2V3ti0_qhVjHe7dwNH<`W{r2cmm9@C;GA>O zPHS`Q7<9cSw+#@^3uNy&y@X&xb>tlHwNYQqJMbcp0D)!h6;1PmF_l@1R(liE7v00< z8*X{Y&*fbyKFt-=dZa0fyLJ2=WmMO>$(#Zf>#mv{+jqsr=jwB~vvy&)w=wDt=)M&B#bPpfe zZJo!QpZNj@P#nk2TWNRz?ao~>l`nT!nkU5hSh)+0urYX+M`%aA4kQZ*$bn5p6Oopd zw!m#uA_es*`d5y%u_>j{`X8b*J;@}tx7is(=FIY`R(I;B$881nB6wJ3kkB!6sbm-3 zzp6{`o;O4D>Mn=r%@6vX7%5(f=b=ESojB|Ut~5};8^w@^aFMV7Bl}A8Cd`Sd5D9LV zCYC4a+>zgwC;Jj`+u2m%EH&MpS9wgzV25s9mHv19r8_r5WSk9zg;_uu>{ictJe57y zZ4dSCzpAX}M+iRe(5yS=#{c3!=@zhs>;xt0sY>Pe z-V959*DBd#IQ{1;VqJ0N>3rw)m&T_Vb7)QJ8OCz}>zUBerk_bbuD!UH7?_(6#YYU8 z5a;05RLlniV0TwaE_HzjW&ay?v~yxbBm`S6Ek?DFvF)vWj9Hn|tC|)^URRmN8htdztLlzeS(pDJaNq%3pq~kw2X|^#TtE|v0>@G8I zDcXw%!5`aB@#L;)SH|BpT!S9j6D6HRsY}6w^WtE1a~e6pu!O=Cn zM*CNMw$b;TUru0N*nXB6-6_@>ca+8&7;m|VnP=DO`N-ue=6Y-i&UeQ+~~xJ_|z zUPxU@{aZsQA*jFZ0TT9OSzT4yRlGz)q|7QD2f6$n7yFW#6o)Qz0UbOE`_S=TNR{8r zJDe;?CF)X!x*G80&dTM|x+aTT3`D3${=C(gDnb+6b@o@F{@9DdNLUA`;T#ub9Br(J zOSUe+a-lkfbJbbO*78$b;v`#Yz>ycPAf(3TgFL?OW0jJOpPmRY!mntu%<|m6e6$0LVS%RjfAF5Pg}`U}3MROV zF`fV9v8$p9I5WO_?D+WQjsqP8*`k}%&5a2$!dkY(NWbXr zpIfVtlh%0q=1AhTzz4bv<|#B!Xp!7Yta-e6=@#~RQ=;{}Zh}{hP9X~p-u{atz%|+h z2TiWcSuDr!CYFUhT;vtf=TV(5A+z6R2j=tP>L3P(wlPMPH+fjCuIk+b9Alj0tCRN= zGQ`KA=*^!?t_bhYT&fEwX9w|$u@7T&#phzwU&@>L`8u(IrQqYqUTCxehe3;sf=lJ3 zSgdn@4eSn>+-v7eOTbB^2sbPmhE9Az5w^u#K5S&lp>#Qk$KQ*+pv}$i_H5NMx zbi8c~u>zua0i*FIdV$rvp#$oI{k!PBS+?g-;Ax zy@b&-m`m4$ROa?$?y$)O@5hVk6V$pB;q8s?XnQ9`;Nzx@1^$B>(Ri~BLS+i^i!;1s z2e1Glt|1k!8PUhr%YGpE|3=`{o^ zfd|bd`5Z0Q-soAw2~@J^mD{4}0uh!Pa0EEGkB6o=29&gqSf=?{Up7~j!gCd7H(ee7 z5Q8Sb%>fph@!`PUqp`psys3fUGBjn0MYt?Sbl!Nrm0Tps<_(v_Pk-S>QUCR483i0vc=0OY zCDV+H!Cuxgf`g1GX13hyOq8VxHgCD8F99+@C z$|34|6rX#1=`S-7@L_>+rWS#}WnMUMimTvgf>up=4H8K%`0&Js3UyU*oxsb5nz@l7 zVFYCdr5UffMy%&TeI=;(0guO7$1;x!jK9On;2-qS^zuiFwZzxW<-&Cd)-Sho##8lDc%| z>XlvY1@?_!1Bm_m^6PEWrv!bHSOBYog7m5JD6# z(#6LS`(PgdaQ8Q42pg@^ajjoUzY!kyxlfGON#E zp~dN`Hq*)g|6%Ozg}PuD3hW~$xX`DI1h3!0bXB^MuvqtR$~z@nSEbrBr&z;Au6dgD z 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

R|Vaq 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 literal 0 HcmV?d00001 diff --git a/docs/theme/404.html b/docs/theme/404.html new file mode 100644 index 0000000000..52beb3b8b3 --- /dev/null +++ b/docs/theme/404.html @@ -0,0 +1,4 @@ +{% extends "base.html" %} +{% block content %} +

404 - Not found

+{% endblock %} diff --git a/docs/theme/assets/javascripts/application.81068b3a.js b/docs/theme/assets/javascripts/application.81068b3a.js new file mode 100644 index 0000000000..476bcf8aeb --- /dev/null +++ b/docs/theme/assets/javascripts/application.81068b3a.js @@ -0,0 +1,6 @@ +!function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=n,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=13)}([function(e,t,n){"use strict";var r={Listener:function(){function e(e,t,n){var r=this;this.els_=Array.prototype.slice.call("string"==typeof e?document.querySelectorAll(e):[].concat(e)),this.handler_="function"==typeof n?{update:n}:n,this.events_=[].concat(t),this.update_=function(e){return r.handler_.update(e)}}var t=e.prototype;return t.listen=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.addEventListener(e,n.update_,!1)})}),"function"==typeof this.handler_.setup&&this.handler_.setup()},t.unlisten=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.removeEventListener(e,n.update_)})}),"function"==typeof this.handler_.reset&&this.handler_.reset()},e}(),MatchMedia:function(e,t){this.handler_=function(e){e.matches?t.listen():t.unlisten()};var n=window.matchMedia(e);n.addListener(this.handler_),this.handler_(n)}},i={Shadow:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement&&n.parentNode instanceof HTMLElement))throw new ReferenceError;if(this.el_=n.parentNode,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLElement))throw new ReferenceError;this.header_=n,this.height_=0,this.active_=!1}var t=e.prototype;return t.setup=function(){for(var e=this.el_;e=e.previousElementSibling;){if(!(e instanceof HTMLElement))throw new ReferenceError;this.height_+=e.offsetHeight}this.update()},t.update=function(e){if(!e||"resize"!==e.type&&"orientationchange"!==e.type){var t=window.pageYOffset>=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=30&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))throw new TypeError;if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+-43;e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(24)}).call(this,n(4))},function(e,f,d){"use strict";(function(t){var e=d(8),n=setTimeout;function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],u(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void c(t);if("function"==typeof n)return void u((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,c(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,c(e)}function c(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],r.forEach(function(e,t){var n,r=a.docs_.get(t),i=f.createElement("li",{class:"md-search-result__item"},f.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article md-search-result__article--document"},f.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(f.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article"},f.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var o=this.el_.parentNode;if(!(o instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&o.offsetHeight>=o.scrollHeight-16;)this.stack_.shift()();var l=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(l,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),r.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",r.size)}}}else{var u=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(u):u(a.data_)},250)}},e}()}).call(this,r(3))},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(14),c(15),c(16),c(17),c(18),c(19),c(20);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))}).listen(),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&document.activeElement.isContentEditable||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else if(document.activeElement&&!document.activeElement.form){if("TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName)return;70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault())}}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){e.exports=n.p+"assets/images/icons/bitbucket.1b09e088.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/github.f0b8504a.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/gitlab.6dd19c00.svg"},function(e,t){e.exports="/Users/chrishounsom/mkdocs-material/material/application.668e8dde.css"},function(e,t){e.exports="/Users/chrishounsom/mkdocs-material/material/application-palette.224b79ff.css"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return(t=t||{}).bubbles=!!t.bubbles,t.cancelable=!!t.cancelable,(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(22),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset .critic.comment:before,.md-typeset .footnote-backref,.md-typeset .task-list-control .task-list-indicator:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before,.md-typeset summary:after{font-family:Material Icons;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-content__icon,.md-footer-nav__button,.md-header-nav__button,.md-nav__button,.md-nav__title:before,.md-search-result__article--document:before{display:inline-block;margin:.2rem;padding:.4rem;font-size:1.2rem;cursor:pointer}.md-icon--arrow-back:before{content:""}.md-icon--arrow-forward:before{content:""}.md-icon--menu:before{content:""}.md-icon--search:before{content:""}[dir=rtl] .md-icon--arrow-back:before{content:""}[dir=rtl] .md-icon--arrow-forward:before{content:""}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern","liga";font-feature-settings:"kern","liga";font-family:Helvetica Neue,Helvetica,Arial,sans-serif}code,kbd,pre{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern";font-feature-settings:"kern";font-family:Courier New,Courier,monospace}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact}.md-typeset blockquote,.md-typeset ol,.md-typeset p,.md-typeset ul{margin:1em 0}.md-typeset h1{margin:0 0 2rem;color:rgba(0,0,0,.54);font-size:1.5625rem;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:2rem 0 .8rem;font-size:1.25rem;line-height:1.4}.md-typeset h3{margin:1.6rem 0 .8rem;font-size:1rem;font-weight:400;letter-spacing:-.01em;line-height:1.5}.md-typeset h2+h3{margin-top:.8rem}.md-typeset h4{font-size:.8rem}.md-typeset h4,.md-typeset h5,.md-typeset h6{margin:.8rem 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset h5{text-transform:uppercase}.md-typeset hr{margin:1.5em 0;border-bottom:.05rem dotted rgba(0,0,0,.26)}.md-typeset a{color:#5700ff;word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color .125s}.md-typeset a:active,.md-typeset a:hover{color:#00e290}.md-typeset code,.md-typeset pre{background-color:hsla(0,0%,92.5%,.5);color:#37474f;font-size:85%;direction:ltr}.md-typeset code{margin:0 .29412em;padding:.07353em 0;border-radius:.1rem;box-shadow:.29412em 0 0 hsla(0,0%,92.5%,.5),-.29412em 0 0 hsla(0,0%,92.5%,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:0;background-color:transparent;box-shadow:none}.md-typeset a>code{margin:inherit;padding:inherit;border-radius:initial;background-color:inherit;color:inherit;box-shadow:none}.md-typeset pre{position:relative;margin:1em 0;border-radius:.1rem;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset pre>code{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;font-size:inherit;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;overflow:auto}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:#00e290}.md-typeset kbd{padding:0 .29412em;border-radius:.15rem;border:.05rem solid #c9c9c9;border-bottom-color:#bcbcbc;background-color:#fcfcfc;color:#555;font-size:85%;box-shadow:0 .05rem 0 #b0b0b0;word-break:break-word}.md-typeset mark{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;background-color:rgba(255,235,59,.5);box-shadow:.25em 0 0 rgba(255,235,59,.5),-.25em 0 0 rgba(255,235,59,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{border-bottom:.05rem dotted rgba(0,0,0,.54);text-decoration:none;cursor:help}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.07812em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.07812em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;border-left:.2rem solid rgba(0,0,0,.26);color:rgba(0,0,0,.54)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid rgba(0,0,0,.26);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset iframe,.md-typeset img,.md-typeset svg{max-width:100%}.md-typeset table:not([class]){box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);display:inline-block;max-width:100%;border-radius:.1rem;font-size:.64rem;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.6rem .8rem;background-color:rgba(0,0,0,.54);color:#fff;vertical-align:top}.md-typeset table:not([class]) td{padding:.6rem .8rem;border-top:.05rem solid rgba(0,0,0,.07);vertical-align:top}.md-typeset table:not([class]) tr{transition:background-color .125s}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:inset 0 .05rem 0 #fff}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;-webkit-overflow-scrolling:touch}.md-typeset .md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}.md-typeset .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{font-size:125%;overflow-x:hidden}body,html{height:100%}body{position:relative;font-size:.5rem}hr{display:block;height:.05rem;padding:0;border:0}.md-svg{display:none}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container,.md-main{overflow:auto}.md-container{display:table;width:100%;height:100%;padding-top:2.4rem;table-layout:fixed}.md-main{display:table-row;height:100%}.md-main__inner{height:100%;padding-top:1.5rem;padding-bottom:.05rem}.md-toggle{display:none}.md-overlay{position:fixed;top:0;width:0;height:0;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);opacity:0;z-index:3}.md-flex{display:table}.md-flex__cell{display:table-cell;position:relative;vertical-align:top}.md-flex__cell--shrink{width:0}.md-flex__cell--stretch{display:table;width:100%;table-layout:fixed}.md-flex__ellipsis{display:table-cell;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-skip{position:fixed;width:.05rem;height:.05rem;margin:.5rem;padding:.3rem .5rem;-webkit-transform:translateY(.4rem);transform:translateY(.4rem);border-radius:.1rem;background-color:rgba(0,0,0,.87);color:#fff;font-size:.64rem;opacity:0;overflow:hidden}.md-skip:focus{width:auto;height:auto;clip:auto;-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;z-index:10}@page{margin:25mm}.md-clipboard{position:absolute;top:.3rem;right:.3rem;width:1.4rem;height:1.4rem;border-radius:.1rem;font-size:.8rem;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-clipboard:before{transition:color .25s,opacity .25s;color:rgba(0,0,0,.07);content:"\E14D"}.codehilite:hover .md-clipboard:before,.md-typeset .highlight:hover .md-clipboard:before,pre:hover .md-clipboard:before{color:rgba(0,0,0,.54)}.md-clipboard:focus:before,.md-clipboard:hover:before{color:#00e290}.md-clipboard__message{display:block;position:absolute;top:0;right:1.7rem;padding:.3rem .5rem;-webkit-transform:translateX(.4rem);transform:translateX(.4rem);transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s;transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);border-radius:.1rem;background-color:rgba(0,0,0,.54);color:#fff;font-size:.64rem;white-space:nowrap;opacity:0;pointer-events:none}.md-clipboard__message--active{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;pointer-events:auto}.md-clipboard__message:before{content:attr(aria-label)}.md-clipboard__message:after{display:block;position:absolute;top:50%;right:-.2rem;width:0;margin-top:-.2rem;border-color:transparent rgba(0,0,0,.54);border-style:solid;border-width:.2rem 0 .2rem .2rem;content:""}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__icon{position:relative;margin:.4rem 0;padding:0;float:right}.md-typeset .md-content__icon{color:rgba(0,0,0,.26)}.md-header{position:fixed;top:0;right:0;left:0;height:2.4rem;transition:background-color .25s,color .25s;background-color:#5700ff;color:#fff;box-shadow:none;z-index:2;-webkit-backface-visibility:hidden;backface-visibility:hidden}.no-js .md-header{transition:none;box-shadow:none}.md-header[data-md-state=shadow]{transition:background-color .25s,color .25s,box-shadow .25s;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.md-header-nav{padding:0 .2rem}.md-header-nav__button{position:relative;transition:opacity .25s;z-index:1}.md-header-nav__button:hover{opacity:.7}.md-header-nav__button.md-logo *{display:block}.no-js .md-header-nav__button.md-icon--search{display:none}.md-header-nav__topic{display:block;position:absolute;transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem)}.no-js .md-header-nav__topic{position:static}.no-js .md-header-nav__topic+.md-header-nav__topic{display:none}.md-header-nav__title{padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.md-header-nav__title[data-md-state=active] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);opacity:1;z-index:0;pointer-events:auto}.md-header-nav__source{display:none}.md-hero{transition:background .25s;background-color:#5700ff;color:#fff;font-size:1rem;overflow:hidden}.md-hero__inner{margin-top:1rem;padding:.8rem .8rem .4rem;transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition-delay:.1s}[data-md-state=hidden] .md-hero__inner{pointer-events:none;-webkit-transform:translateY(.625rem);transform:translateY(.625rem);transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:transform 0s .4s,opacity .1s 0s;transition:transform 0s .4s,opacity .1s 0s,-webkit-transform 0s .4s;opacity:0}.md-hero--expand .md-hero__inner{margin-bottom:1.2rem}.md-footer-nav{background-color:rgba(0,0,0,.87);color:#fff}.md-footer-nav__inner{padding:.2rem;overflow:auto}.md-footer-nav__link{padding-top:1.4rem;padding-bottom:.4rem;transition:opacity .25s}.md-footer-nav__link:hover{opacity:.7}.md-footer-nav__link--prev{width:25%;float:left}[dir=rtl] .md-footer-nav__link--prev{float:right}.md-footer-nav__link--next{width:75%;float:right;text-align:right}[dir=rtl] .md-footer-nav__link--next{float:left;text-align:left}.md-footer-nav__button{transition:background .25s}.md-footer-nav__title{position:relative;padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer-nav__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;color:hsla(0,0%,100%,.7);font-size:.75rem}.md-footer-meta{background-color:rgba(0,0,0,.895)}.md-footer-meta__inner{padding:.2rem;overflow:auto}.md-footer-meta__help{background-color:#00e290}html .md-footer-meta.md-typeset .md-footer-meta__help a{margin:0 .6rem;color:rgba(0,0,0,.54)}html .md-footer-meta.md-typeset .md-footer-meta__help a:focus,html .md-footer-meta.md-typeset .md-footer-meta__help a:hover{color:#fff}html .md-footer-meta.md-typeset a{color:hsla(0,0%,100%,.7)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:#fff}.md-footer-copyright{margin:0 .6rem;padding:.4rem 0;color:hsla(0,0%,100%,.3);font-size:.64rem}.md-footer-copyright__highlight{color:hsla(0,0%,100%,.7)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;font-size:.8rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden}.md-nav__title:before{display:none;content:"\E5C4"}[dir=rtl] .md-nav__title:before{content:"\E5C8"}.md-nav__title .md-nav__button{display:none}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item:last-child{padding-bottom:.6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__item .md-nav__item:last-child{padding-bottom:0}.md-nav__button img{width:100%;height:auto}.md-nav__link{display:block;margin-top:.625em;transition:color .125s;text-overflow:ellipsis;cursor:pointer;overflow:hidden}.md-nav__item--nested>.md-nav__link:after{content:"\E313"}html .md-nav__link[for=__toc],html .md-nav__link[for=__toc]+.md-nav__link:after,html .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link[data-md-state=blur]{color:rgba(0,0,0,.54)}.md-nav__link--active,.md-nav__link:active{color:#5700ff}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:#00e290}.md-nav__source,.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}.md-search__form{position:relative}.md-search__input{position:relative;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;z-index:2}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input:-ms-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-ms-input-placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::placeholder{transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-webkit-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input:-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-clear{display:none}.md-search__icon{position:absolute;transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;font-size:1.2rem;cursor:pointer;z-index:2}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}.md-search__icon[for=__search]:before{content:"\E8B6"}.md-search__icon[type=reset]{top:.3rem;right:.5rem;-webkit-transform:scale(.125);transform:scale(.125);transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);opacity:0}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{-webkit-transform:scale(1);transform:scale(1);opacity:1}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;width:100%;border-radius:0 0 .1rem .1rem;overflow:hidden;z-index:1}.md-search__scrollwrap{height:100%;background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07);overflow-y:auto;-webkit-overflow-scrolling:touch}.md-search-result{color:rgba(0,0,0,.87);word-break:break-word}.md-search-result__meta{padding:0 .8rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.8rem}.md-search-result__list{margin:0;padding:0;border-top:.05rem solid rgba(0,0,0,.07);list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 rgba(0,0,0,.07)}.md-search-result__link{display:block;transition:background .25s;outline:0;overflow:hidden}.md-search-result__link:hover,.md-search-result__link[data-md-state=active]{background-color:rgba(0,226,144,.1)}.md-search-result__link:hover .md-search-result__article:before,.md-search-result__link[data-md-state=active] .md-search-result__article:before{opacity:.7}.md-search-result__link:last-child .md-search-result__teaser{margin-bottom:.6rem}.md-search-result__article{position:relative;padding:0 .8rem;overflow:auto}.md-search-result__article--document:before{position:absolute;left:0;margin:.1rem;transition:opacity .25s;color:rgba(0,0,0,.54);content:"\E880"}[dir=rtl] .md-search-result__article--document:before{right:0;left:auto}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-size:.8rem;font-weight:400;line-height:1.4}.md-search-result__title{margin:.5em 0;font-size:.64rem;font-weight:700;line-height:1.4}.md-search-result__teaser{display:-webkit-box;max-height:1.65rem;margin:.5em 0;color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.4;text-overflow:ellipsis;overflow:hidden;-webkit-line-clamp:2}.md-search-result em{font-style:normal;font-weight:700;text-decoration:underline}.md-sidebar{position:absolute;width:12.1rem;padding:1.2rem 0;overflow:hidden}.md-sidebar[data-md-state=lock]{position:fixed;top:2.4rem}.md-sidebar--secondary{display:none}.md-sidebar__scrollwrap{max-height:100%;margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00e290}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.md-source{display:block;padding-right:.6rem;transition:opacity .25s;font-size:.65rem;line-height:1.2;white-space:nowrap}[dir=rtl] .md-source{padding-right:0;padding-left:.6rem}.md-source:hover{opacity:.7}.md-source:after,.md-source__icon{display:inline-block;height:2.4rem;content:"";vertical-align:middle}.md-source__icon{width:2.4rem}.md-source__icon svg{width:1.2rem;height:1.2rem;margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2.2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2.2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:100%;margin-left:.6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden;vertical-align:middle}.md-source__facts{margin:0;padding:0;font-size:.55rem;font-weight:700;list-style-type:none;opacity:.75;overflow:hidden}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"\00B7"}.md-source__fact:first-child:before{display:none}.md-source-file{display:inline-block;margin:1em .5em 1em 0;padding-right:.25rem;border-radius:.1rem;background-color:rgba(0,0,0,.07);font-size:.64rem;list-style-type:none;cursor:pointer;overflow:hidden}.md-source-file:before{display:inline-block;margin-right:.25rem;padding:.25rem;background-color:rgba(0,0,0,.26);color:#fff;font-size:.8rem;content:"\E86F";vertical-align:middle}html .md-source-file{transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1)}html .md-source-file:before{transition:inherit}html body .md-typeset .md-source-file{color:rgba(0,0,0,.54)}.md-source-file:hover{box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36)}.md-source-file:hover:before{background-color:#00e290}.md-tabs{width:100%;transition:background .25s;background-color:#5700ff;color:#fff;overflow:auto}.md-tabs__list{margin:0 0 0 .2rem;padding:0;list-style:none;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);font-size:.7rem;opacity:.7}.md-tabs__link--active,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:.02s}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:.04s}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:.06s}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:.08s}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs[data-md-state=hidden] .md-tabs__link{-webkit-transform:translateY(50%);transform:translateY(50%);transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,transform 0s .4s,opacity .1s;transition:color .25s,transform 0s .4s,opacity .1s,-webkit-transform 0s .4s;opacity:0}.md-typeset .admonition,.md-typeset details{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin:1.5625em 0;padding:0 .6rem;border-left:.2rem solid #448aff;border-radius:.1rem;font-size:.64rem;overflow:auto}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition>.admonition-title,.md-typeset .admonition>summary,.md-typeset details>.admonition-title,.md-typeset details>summary{margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;border-bottom:.05rem solid rgba(68,138,255,.1);background-color:rgba(68,138,255,.1);font-weight:700}[dir=rtl] .md-typeset .admonition>.admonition-title,[dir=rtl] .md-typeset .admonition>summary,[dir=rtl] .md-typeset details>.admonition-title,[dir=rtl] .md-typeset details>summary{padding:.4rem 2rem .4rem .6rem}.md-typeset .admonition>.admonition-title:last-child,.md-typeset .admonition>summary:last-child,.md-typeset details>.admonition-title:last-child,.md-typeset details>summary:last-child{margin-bottom:0}.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before{position:absolute;left:.6rem;color:#448aff;font-size:1rem;content:"\E3C9"}[dir=rtl] .md-typeset .admonition>.admonition-title:before,[dir=rtl] .md-typeset .admonition>summary:before,[dir=rtl] .md-typeset details>.admonition-title:before,[dir=rtl] .md-typeset details>summary:before{right:.6rem;left:auto}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-left-color:#00b0ff}[dir=rtl] .md-typeset .admonition.abstract,[dir=rtl] .md-typeset .admonition.summary,[dir=rtl] .md-typeset .admonition.tldr,[dir=rtl] .md-typeset details.abstract,[dir=rtl] .md-typeset details.summary,[dir=rtl] .md-typeset details.tldr{border-right-color:#00b0ff}.md-typeset .admonition.abstract>.admonition-title,.md-typeset .admonition.abstract>summary,.md-typeset .admonition.summary>.admonition-title,.md-typeset .admonition.summary>summary,.md-typeset .admonition.tldr>.admonition-title,.md-typeset .admonition.tldr>summary,.md-typeset details.abstract>.admonition-title,.md-typeset details.abstract>summary,.md-typeset details.summary>.admonition-title,.md-typeset details.summary>summary,.md-typeset details.tldr>.admonition-title,.md-typeset details.tldr>summary{border-bottom-color:rgba(0,176,255,.1);background-color:rgba(0,176,255,.1)}.md-typeset .admonition.abstract>.admonition-title:before,.md-typeset .admonition.abstract>summary:before,.md-typeset .admonition.summary>.admonition-title:before,.md-typeset .admonition.summary>summary:before,.md-typeset .admonition.tldr>.admonition-title:before,.md-typeset .admonition.tldr>summary:before,.md-typeset details.abstract>.admonition-title:before,.md-typeset details.abstract>summary:before,.md-typeset details.summary>.admonition-title:before,.md-typeset details.summary>summary:before,.md-typeset details.tldr>.admonition-title:before,.md-typeset details.tldr>summary:before{color:#00b0ff;content:""}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-left-color:#00b8d4}[dir=rtl] .md-typeset .admonition.info,[dir=rtl] .md-typeset .admonition.todo,[dir=rtl] .md-typeset details.info,[dir=rtl] .md-typeset details.todo{border-right-color:#00b8d4}.md-typeset .admonition.info>.admonition-title,.md-typeset .admonition.info>summary,.md-typeset .admonition.todo>.admonition-title,.md-typeset .admonition.todo>summary,.md-typeset details.info>.admonition-title,.md-typeset details.info>summary,.md-typeset details.todo>.admonition-title,.md-typeset details.todo>summary{border-bottom-color:rgba(0,184,212,.1);background-color:rgba(0,184,212,.1)}.md-typeset .admonition.info>.admonition-title:before,.md-typeset .admonition.info>summary:before,.md-typeset .admonition.todo>.admonition-title:before,.md-typeset .admonition.todo>summary:before,.md-typeset details.info>.admonition-title:before,.md-typeset details.info>summary:before,.md-typeset details.todo>.admonition-title:before,.md-typeset details.todo>summary:before{color:#00b8d4;content:""}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-left-color:#00bfa5}[dir=rtl] .md-typeset .admonition.hint,[dir=rtl] .md-typeset .admonition.important,[dir=rtl] .md-typeset .admonition.tip,[dir=rtl] .md-typeset details.hint,[dir=rtl] .md-typeset details.important,[dir=rtl] .md-typeset details.tip{border-right-color:#00bfa5}.md-typeset .admonition.hint>.admonition-title,.md-typeset .admonition.hint>summary,.md-typeset .admonition.important>.admonition-title,.md-typeset .admonition.important>summary,.md-typeset .admonition.tip>.admonition-title,.md-typeset .admonition.tip>summary,.md-typeset details.hint>.admonition-title,.md-typeset details.hint>summary,.md-typeset details.important>.admonition-title,.md-typeset details.important>summary,.md-typeset details.tip>.admonition-title,.md-typeset details.tip>summary{border-bottom-color:rgba(0,191,165,.1);background-color:rgba(0,191,165,.1)}.md-typeset .admonition.hint>.admonition-title:before,.md-typeset .admonition.hint>summary:before,.md-typeset .admonition.important>.admonition-title:before,.md-typeset .admonition.important>summary:before,.md-typeset .admonition.tip>.admonition-title:before,.md-typeset .admonition.tip>summary:before,.md-typeset details.hint>.admonition-title:before,.md-typeset details.hint>summary:before,.md-typeset details.important>.admonition-title:before,.md-typeset details.important>summary:before,.md-typeset details.tip>.admonition-title:before,.md-typeset details.tip>summary:before{color:#00bfa5;content:""}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-left-color:#00c853}[dir=rtl] .md-typeset .admonition.check,[dir=rtl] .md-typeset .admonition.done,[dir=rtl] .md-typeset .admonition.success,[dir=rtl] .md-typeset details.check,[dir=rtl] .md-typeset details.done,[dir=rtl] .md-typeset details.success{border-right-color:#00c853}.md-typeset .admonition.check>.admonition-title,.md-typeset .admonition.check>summary,.md-typeset .admonition.done>.admonition-title,.md-typeset .admonition.done>summary,.md-typeset .admonition.success>.admonition-title,.md-typeset .admonition.success>summary,.md-typeset details.check>.admonition-title,.md-typeset details.check>summary,.md-typeset details.done>.admonition-title,.md-typeset details.done>summary,.md-typeset details.success>.admonition-title,.md-typeset details.success>summary{border-bottom-color:rgba(0,200,83,.1);background-color:rgba(0,200,83,.1)}.md-typeset .admonition.check>.admonition-title:before,.md-typeset .admonition.check>summary:before,.md-typeset .admonition.done>.admonition-title:before,.md-typeset .admonition.done>summary:before,.md-typeset .admonition.success>.admonition-title:before,.md-typeset .admonition.success>summary:before,.md-typeset details.check>.admonition-title:before,.md-typeset details.check>summary:before,.md-typeset details.done>.admonition-title:before,.md-typeset details.done>summary:before,.md-typeset details.success>.admonition-title:before,.md-typeset details.success>summary:before{color:#00c853;content:""}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-left-color:#64dd17}[dir=rtl] .md-typeset .admonition.faq,[dir=rtl] .md-typeset .admonition.help,[dir=rtl] .md-typeset .admonition.question,[dir=rtl] .md-typeset details.faq,[dir=rtl] .md-typeset details.help,[dir=rtl] .md-typeset details.question{border-right-color:#64dd17}.md-typeset .admonition.faq>.admonition-title,.md-typeset .admonition.faq>summary,.md-typeset .admonition.help>.admonition-title,.md-typeset .admonition.help>summary,.md-typeset .admonition.question>.admonition-title,.md-typeset .admonition.question>summary,.md-typeset details.faq>.admonition-title,.md-typeset details.faq>summary,.md-typeset details.help>.admonition-title,.md-typeset details.help>summary,.md-typeset details.question>.admonition-title,.md-typeset details.question>summary{border-bottom-color:rgba(100,221,23,.1);background-color:rgba(100,221,23,.1)}.md-typeset .admonition.faq>.admonition-title:before,.md-typeset .admonition.faq>summary:before,.md-typeset .admonition.help>.admonition-title:before,.md-typeset .admonition.help>summary:before,.md-typeset .admonition.question>.admonition-title:before,.md-typeset .admonition.question>summary:before,.md-typeset details.faq>.admonition-title:before,.md-typeset details.faq>summary:before,.md-typeset details.help>.admonition-title:before,.md-typeset details.help>summary:before,.md-typeset details.question>.admonition-title:before,.md-typeset details.question>summary:before{color:#64dd17;content:""}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-left-color:#ff9100}[dir=rtl] .md-typeset .admonition.attention,[dir=rtl] .md-typeset .admonition.caution,[dir=rtl] .md-typeset .admonition.warning,[dir=rtl] .md-typeset details.attention,[dir=rtl] .md-typeset details.caution,[dir=rtl] .md-typeset details.warning{border-right-color:#ff9100}.md-typeset .admonition.attention>.admonition-title,.md-typeset .admonition.attention>summary,.md-typeset .admonition.caution>.admonition-title,.md-typeset .admonition.caution>summary,.md-typeset .admonition.warning>.admonition-title,.md-typeset .admonition.warning>summary,.md-typeset details.attention>.admonition-title,.md-typeset details.attention>summary,.md-typeset details.caution>.admonition-title,.md-typeset details.caution>summary,.md-typeset details.warning>.admonition-title,.md-typeset details.warning>summary{border-bottom-color:rgba(255,145,0,.1);background-color:rgba(255,145,0,.1)}.md-typeset .admonition.attention>.admonition-title:before,.md-typeset .admonition.attention>summary:before,.md-typeset .admonition.caution>.admonition-title:before,.md-typeset .admonition.caution>summary:before,.md-typeset .admonition.warning>.admonition-title:before,.md-typeset .admonition.warning>summary:before,.md-typeset details.attention>.admonition-title:before,.md-typeset details.attention>summary:before,.md-typeset details.caution>.admonition-title:before,.md-typeset details.caution>summary:before,.md-typeset details.warning>.admonition-title:before,.md-typeset details.warning>summary:before{color:#ff9100;content:""}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-left-color:#ff5252}[dir=rtl] .md-typeset .admonition.fail,[dir=rtl] .md-typeset .admonition.failure,[dir=rtl] .md-typeset .admonition.missing,[dir=rtl] .md-typeset details.fail,[dir=rtl] .md-typeset details.failure,[dir=rtl] .md-typeset details.missing{border-right-color:#ff5252}.md-typeset .admonition.fail>.admonition-title,.md-typeset .admonition.fail>summary,.md-typeset .admonition.failure>.admonition-title,.md-typeset .admonition.failure>summary,.md-typeset .admonition.missing>.admonition-title,.md-typeset .admonition.missing>summary,.md-typeset details.fail>.admonition-title,.md-typeset details.fail>summary,.md-typeset details.failure>.admonition-title,.md-typeset details.failure>summary,.md-typeset details.missing>.admonition-title,.md-typeset details.missing>summary{border-bottom-color:rgba(255,82,82,.1);background-color:rgba(255,82,82,.1)}.md-typeset .admonition.fail>.admonition-title:before,.md-typeset .admonition.fail>summary:before,.md-typeset .admonition.failure>.admonition-title:before,.md-typeset .admonition.failure>summary:before,.md-typeset .admonition.missing>.admonition-title:before,.md-typeset .admonition.missing>summary:before,.md-typeset details.fail>.admonition-title:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>.admonition-title:before,.md-typeset details.failure>summary:before,.md-typeset details.missing>.admonition-title:before,.md-typeset details.missing>summary:before{color:#ff5252;content:""}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-left-color:#ff1744}[dir=rtl] .md-typeset .admonition.danger,[dir=rtl] .md-typeset .admonition.error,[dir=rtl] .md-typeset details.danger,[dir=rtl] .md-typeset details.error{border-right-color:#ff1744}.md-typeset .admonition.danger>.admonition-title,.md-typeset .admonition.danger>summary,.md-typeset .admonition.error>.admonition-title,.md-typeset .admonition.error>summary,.md-typeset details.danger>.admonition-title,.md-typeset details.danger>summary,.md-typeset details.error>.admonition-title,.md-typeset details.error>summary{border-bottom-color:rgba(255,23,68,.1);background-color:rgba(255,23,68,.1)}.md-typeset .admonition.danger>.admonition-title:before,.md-typeset .admonition.danger>summary:before,.md-typeset .admonition.error>.admonition-title:before,.md-typeset .admonition.error>summary:before,.md-typeset details.danger>.admonition-title:before,.md-typeset details.danger>summary:before,.md-typeset details.error>.admonition-title:before,.md-typeset details.error>summary:before{color:#ff1744;content:""}.md-typeset .admonition.bug,.md-typeset details.bug{border-left-color:#f50057}[dir=rtl] .md-typeset .admonition.bug,[dir=rtl] .md-typeset details.bug{border-right-color:#f50057}.md-typeset .admonition.bug>.admonition-title,.md-typeset .admonition.bug>summary,.md-typeset details.bug>.admonition-title,.md-typeset details.bug>summary{border-bottom-color:rgba(245,0,87,.1);background-color:rgba(245,0,87,.1)}.md-typeset .admonition.bug>.admonition-title:before,.md-typeset .admonition.bug>summary:before,.md-typeset details.bug>.admonition-title:before,.md-typeset details.bug>summary:before{color:#f50057;content:""}.md-typeset .admonition.example,.md-typeset details.example{border-left-color:#651fff}[dir=rtl] .md-typeset .admonition.example,[dir=rtl] .md-typeset details.example{border-right-color:#651fff}.md-typeset .admonition.example>.admonition-title,.md-typeset .admonition.example>summary,.md-typeset details.example>.admonition-title,.md-typeset details.example>summary{border-bottom-color:rgba(101,31,255,.1);background-color:rgba(101,31,255,.1)}.md-typeset .admonition.example>.admonition-title:before,.md-typeset .admonition.example>summary:before,.md-typeset details.example>.admonition-title:before,.md-typeset details.example>summary:before{color:#651fff;content:""}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-left-color:#9e9e9e}[dir=rtl] .md-typeset .admonition.cite,[dir=rtl] .md-typeset .admonition.quote,[dir=rtl] .md-typeset details.cite,[dir=rtl] .md-typeset details.quote{border-right-color:#9e9e9e}.md-typeset .admonition.cite>.admonition-title,.md-typeset .admonition.cite>summary,.md-typeset .admonition.quote>.admonition-title,.md-typeset .admonition.quote>summary,.md-typeset details.cite>.admonition-title,.md-typeset details.cite>summary,.md-typeset details.quote>.admonition-title,.md-typeset details.quote>summary{border-bottom-color:hsla(0,0%,62%,.1);background-color:hsla(0,0%,62%,.1)}.md-typeset .admonition.cite>.admonition-title:before,.md-typeset .admonition.cite>summary:before,.md-typeset .admonition.quote>.admonition-title:before,.md-typeset .admonition.quote>summary:before,.md-typeset details.cite>.admonition-title:before,.md-typeset details.cite>summary:before,.md-typeset details.quote>.admonition-title:before,.md-typeset details.quote>summary:before{color:#9e9e9e;content:""}.codehilite .o,.codehilite .ow,.md-typeset .highlight .o,.md-typeset .highlight .ow{color:inherit}.codehilite .ge,.md-typeset .highlight .ge{color:#000}.codehilite .gr,.md-typeset .highlight .gr{color:#a00}.codehilite .gh,.md-typeset .highlight .gh{color:#999}.codehilite .go,.md-typeset .highlight .go{color:#888}.codehilite .gp,.md-typeset .highlight .gp{color:#555}.codehilite .gs,.md-typeset .highlight .gs{color:inherit}.codehilite .gu,.md-typeset .highlight .gu{color:#aaa}.codehilite .gt,.md-typeset .highlight .gt{color:#a00}.codehilite .gd,.md-typeset .highlight .gd{background-color:#fdd}.codehilite .gi,.md-typeset .highlight .gi{background-color:#dfd}.codehilite .k,.md-typeset .highlight .k{color:#3b78e7}.codehilite .kc,.md-typeset .highlight .kc{color:#a71d5d}.codehilite .kd,.codehilite .kn,.md-typeset .highlight .kd,.md-typeset .highlight .kn{color:#3b78e7}.codehilite .kp,.md-typeset .highlight .kp{color:#a71d5d}.codehilite .kr,.codehilite .kt,.md-typeset .highlight .kr,.md-typeset .highlight .kt{color:#3e61a2}.codehilite .c,.codehilite .cm,.md-typeset .highlight .c,.md-typeset .highlight .cm{color:#999}.codehilite .cp,.md-typeset .highlight .cp{color:#666}.codehilite .c1,.codehilite .ch,.codehilite .cs,.md-typeset .highlight .c1,.md-typeset .highlight .ch,.md-typeset .highlight .cs{color:#999}.codehilite .na,.codehilite .nb,.md-typeset .highlight .na,.md-typeset .highlight .nb{color:#c2185b}.codehilite .bp,.md-typeset .highlight .bp{color:#3e61a2}.codehilite .nc,.md-typeset .highlight .nc{color:#c2185b}.codehilite .no,.md-typeset .highlight .no{color:#3e61a2}.codehilite .nd,.codehilite .ni,.md-typeset .highlight .nd,.md-typeset .highlight .ni{color:#666}.codehilite .ne,.codehilite .nf,.md-typeset .highlight .ne,.md-typeset .highlight .nf{color:#c2185b}.codehilite .nl,.md-typeset .highlight .nl{color:#3b5179}.codehilite .nn,.md-typeset .highlight .nn{color:#ec407a}.codehilite .nt,.md-typeset .highlight .nt{color:#3b78e7}.codehilite .nv,.codehilite .vc,.codehilite .vg,.codehilite .vi,.md-typeset .highlight .nv,.md-typeset .highlight .vc,.md-typeset .highlight .vg,.md-typeset .highlight .vi{color:#3e61a2}.codehilite .nx,.md-typeset .highlight .nx{color:#ec407a}.codehilite .il,.codehilite .m,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.md-typeset .highlight .il,.md-typeset .highlight .m,.md-typeset .highlight .mf,.md-typeset .highlight .mh,.md-typeset .highlight .mi,.md-typeset .highlight .mo{color:#e74c3c}.codehilite .s,.codehilite .sb,.codehilite .sc,.md-typeset .highlight .s,.md-typeset .highlight .sb,.md-typeset .highlight .sc{color:#0d904f}.codehilite .sd,.md-typeset .highlight .sd{color:#999}.codehilite .s2,.md-typeset .highlight .s2{color:#0d904f}.codehilite .se,.codehilite .sh,.codehilite .si,.codehilite .sx,.md-typeset .highlight .se,.md-typeset .highlight .sh,.md-typeset .highlight .si,.md-typeset .highlight .sx{color:#183691}.codehilite .sr,.md-typeset .highlight .sr{color:#009926}.codehilite .s1,.codehilite .ss,.md-typeset .highlight .s1,.md-typeset .highlight .ss{color:#0d904f}.codehilite .err,.md-typeset .highlight .err{color:#a61717}.codehilite .w,.md-typeset .highlight .w{color:transparent}.codehilite .hll,.md-typeset .highlight .hll{display:block;margin:0 -.6rem;padding:0 .6rem;background-color:rgba(255,235,59,.5)}.md-typeset .codehilite,.md-typeset .highlight{position:relative;margin:1em 0;padding:0;border-radius:.1rem;background-color:hsla(0,0%,92.5%,.5);color:#37474f;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset .codehilite code,.md-typeset .codehilite pre,.md-typeset .highlight code,.md-typeset .highlight pre{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;overflow:auto;vertical-align:top}.md-typeset .codehilite code::-webkit-scrollbar,.md-typeset .codehilite pre::-webkit-scrollbar,.md-typeset .highlight code::-webkit-scrollbar,.md-typeset .highlight pre::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset .codehilite code::-webkit-scrollbar-thumb,.md-typeset .codehilite pre::-webkit-scrollbar-thumb,.md-typeset .highlight code::-webkit-scrollbar-thumb,.md-typeset .highlight pre::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset .codehilite code::-webkit-scrollbar-thumb:hover,.md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,.md-typeset .highlight code::-webkit-scrollbar-thumb:hover,.md-typeset .highlight pre::-webkit-scrollbar-thumb:hover{background-color:#00e290}.md-typeset pre.codehilite,.md-typeset pre.highlight{overflow:visible}.md-typeset pre.codehilite code,.md-typeset pre.highlight code{display:block;padding:.525rem .6rem;overflow:auto}.md-typeset .codehilitetable,.md-typeset .highlighttable{display:block;margin:1em 0;border-radius:.2em;font-size:.8rem;overflow:hidden}.md-typeset .codehilitetable tbody,.md-typeset .codehilitetable td,.md-typeset .highlighttable tbody,.md-typeset .highlighttable td{display:block;padding:0}.md-typeset .codehilitetable tr,.md-typeset .highlighttable tr{display:flex}.md-typeset .codehilitetable .codehilite,.md-typeset .codehilitetable .highlight,.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .codehilite,.md-typeset .highlighttable .highlight,.md-typeset .highlighttable .linenodiv{margin:0;border-radius:0}.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .linenodiv{padding:.525rem .6rem}.md-typeset .codehilitetable .linenos,.md-typeset .highlighttable .linenos{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.26);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset .codehilitetable .linenos pre,.md-typeset .highlighttable .linenos pre{margin:0;padding:0;background-color:transparent;color:inherit;text-align:right}.md-typeset .codehilitetable .code,.md-typeset .highlighttable .code{flex:1;overflow:hidden}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{box-shadow:none}.md-typeset [id^="fnref:"]{display:inline-block}.md-typeset [id^="fnref:"]:target{margin-top:-3.8rem;padding-top:3.8rem;pointer-events:none}.md-typeset [id^="fn:"]:before{display:none;height:0;content:""}.md-typeset [id^="fn:"]:target:before{display:block;margin-top:-3.5rem;padding-top:3.5rem;pointer-events:none}.md-typeset .footnote{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{transition:color .25s}.md-typeset .footnote li:target{color:rgba(0,0,0,.87)}.md-typeset .footnote li :first-child{margin-top:0}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}.md-typeset .footnote li:hover .footnote-backref:hover,.md-typeset .footnote li:target .footnote-backref{color:#00e290}.md-typeset .footnote-ref{display:inline-block;pointer-events:auto}.md-typeset .footnote-ref:before{display:inline;margin:0 .2em;border-left:.05rem solid rgba(0,0,0,.26);font-size:1.25em;content:"";vertical-align:-.25rem}.md-typeset .footnote-backref{display:inline-block;-webkit-transform:translateX(.25rem);transform:translateX(.25rem);transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s,-webkit-transform .25s .125s;color:rgba(0,0,0,.26);font-size:0;opacity:0;vertical-align:text-bottom}[dir=rtl] .md-typeset .footnote-backref{-webkit-transform:translateX(-.25rem);transform:translateX(-.25rem)}.md-typeset .footnote-backref:before{display:inline-block;font-size:.8rem;content:"\E31B"}[dir=rtl] .md-typeset .footnote-backref:before{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;-webkit-transform:translateY(.25rem);transform:translateY(.25rem);transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s,-webkit-transform .25s .25s;opacity:0}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}html body .md-typeset .headerlink{color:rgba(0,0,0,.26)}.md-typeset h1[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h1[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink,.md-typeset h1[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink:hover,.md-typeset h1[id]:target .headerlink{color:#00e290}.md-typeset h2[id]:before{display:block;margin-top:-8px;padding-top:8px;content:""}.md-typeset h2[id]:target:before{margin-top:-3.4rem;padding-top:3.4rem}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink,.md-typeset h2[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink:hover,.md-typeset h2[id]:target .headerlink{color:#00e290}.md-typeset h3[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h3[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink,.md-typeset h3[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink:hover,.md-typeset h3[id]:target .headerlink{color:#00e290}.md-typeset h4[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h4[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink,.md-typeset h4[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink:hover,.md-typeset h4[id]:target .headerlink{color:#00e290}.md-typeset h5[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h5[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink,.md-typeset h5[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink:hover,.md-typeset h5[id]:target .headerlink{color:#00e290}.md-typeset h6[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h6[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink,.md-typeset h6[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink:hover,.md-typeset h6[id]:target .headerlink{color:#00e290}.md-typeset .MJXc-display{margin:.75em 0;padding:.75em 0;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset .MathJax_CHTML{outline:0}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:#fdd;box-shadow:.25em 0 0 #fdd,-.25em 0 0 #fdd}.md-typeset ins.critic{background-color:#dfd;box-shadow:.25em 0 0 #dfd,-.25em 0 0 #dfd}.md-typeset .critic.comment{background-color:hsla(0,0%,92.5%,.5);color:#37474f;box-shadow:.25em 0 0 hsla(0,0%,92.5%,.5),-.25em 0 0 hsla(0,0%,92.5%,.5)}.md-typeset .critic.comment:before{padding-right:.125em;color:rgba(0,0,0,.26);content:"\E0B7";vertical-align:-.125em}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;box-shadow:none}.md-typeset .critic.block :first-child{margin-top:.5em}.md-typeset .critic.block :last-child{margin-bottom:.5em}.md-typeset details{display:block;padding-top:0}.md-typeset details[open]>summary:after{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.md-typeset details:not([open]){padding-bottom:0}.md-typeset details:not([open])>summary{border-bottom:none}.md-typeset details summary{padding-right:2rem}[dir=rtl] .md-typeset details summary{padding-left:2rem}.no-details .md-typeset details:not([open])>*{display:none}.no-details .md-typeset details:not([open]) summary{display:block}.md-typeset summary{display:block;outline:none;cursor:pointer}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset summary:after{position:absolute;top:.4rem;right:.6rem;color:rgba(0,0,0,.26);font-size:1rem;content:"\E313"}[dir=rtl] .md-typeset summary:after{right:auto;left:.6rem}.md-typeset .emojione{width:1rem;vertical-align:text-top}.md-typeset code.codehilite,.md-typeset code.highlight{margin:0 .29412em;padding:.07353em 0}.md-typeset .superfences-content{display:none;order:99;width:100%;background-color:#fff}.md-typeset .superfences-content>*{margin:0;border-radius:0}.md-typeset .superfences-tabs{display:flex;position:relative;flex-wrap:wrap;margin:1em 0;border:.05rem solid rgba(0,0,0,.07);border-radius:.2em}.md-typeset .superfences-tabs>input{display:none}.md-typeset .superfences-tabs>input:checked+label{font-weight:700}.md-typeset .superfences-tabs>input:checked+label+.superfences-content{display:block}.md-typeset .superfences-tabs>label{width:auto;padding:.6rem;transition:color .125s;font-size:.64rem;cursor:pointer}html .md-typeset .superfences-tabs>label:hover{color:#00e290}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control .task-list-indicator:before{position:absolute;top:.15em;left:-1.25em;color:rgba(0,0,0,.26);font-size:1.25em;content:"\E835";vertical-align:-.25em}[dir=rtl] .md-typeset .task-list-control .task-list-indicator:before{right:-1.25em;left:auto}.md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator:before{content:"\E834"}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}@media print{.md-typeset a:after{color:rgba(0,0,0,.54);content:" [" attr(href) "]"}.md-typeset code,.md-typeset pre{white-space:pre-wrap}.md-typeset code{box-shadow:none;-webkit-box-decoration-break:initial;box-decoration-break:slice}.md-clipboard,.md-content__icon,.md-footer,.md-header,.md-sidebar,.md-tabs,.md-typeset .headerlink{display:none}}@media only screen and (max-width:44.9375em){.md-typeset pre{margin:1em -.8rem;border-radius:0}.md-typeset pre>code{padding:.525rem .8rem}.md-footer-nav__link--prev .md-footer-nav__title{display:none}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}.codehilite .hll,.md-typeset .highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.codehilite,.md-typeset>.highlight{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilite code,.md-typeset>.codehilite pre,.md-typeset>.highlight code,.md-typeset>.highlight pre{padding:.525rem .8rem}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilitetable .codehilite>code,.md-typeset>.codehilitetable .codehilite>pre,.md-typeset>.codehilitetable .highlight>code,.md-typeset>.codehilitetable .highlight>pre,.md-typeset>.codehilitetable .linenodiv,.md-typeset>.highlighttable .codehilite>code,.md-typeset>.highlighttable .codehilite>pre,.md-typeset>.highlighttable .highlight>code,.md-typeset>.highlighttable .highlight>pre,.md-typeset>.highlighttable .linenodiv{padding:.5rem .8rem}.md-typeset>p>.MJXc-display{margin:.75em -.8rem;padding:.25em .8rem}.md-typeset>.superfences-tabs{margin:1em -.8rem;border:0;border-top:.05rem solid rgba(0,0,0,.07);border-radius:0}.md-typeset>.superfences-tabs code,.md-typeset>.superfences-tabs pre{padding:.525rem .8rem}}@media only screen and (min-width:100em){html{font-size:137.5%}}@media only screen and (min-width:125em){html{font-size:150%}}@media only screen and (max-width:59.9375em){body[data-md-state=lock]{overflow:hidden}.ios body[data-md-state=lock] .md-container{display:none}html .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}html .md-nav__link[for=__toc]:after{color:inherit;content:"\E8DE"}html .md-nav__link[for=__toc]+.md-nav__link{display:none}html .md-nav__link[for=__toc]~.md-nav{display:flex}html [dir=rtl] .md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav__source{display:block;padding:0 .2rem;background-color:rgba(69,0,203,.9675);color:#fff}.md-search__overlay{position:absolute;top:.2rem;left:.2rem;width:1.8rem;height:1.8rem;-webkit-transform-origin:center;transform-origin:center;transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:transform .3s .1s,opacity .2s .2s;transition:transform .3s .1s,opacity .2s .2s,-webkit-transform .3s .1s;border-radius:1rem;background-color:#fff;overflow:hidden;pointer-events:none}[dir=rtl] .md-search__overlay{right:.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{transition:opacity .1s,-webkit-transform .4s;transition:transform .4s,opacity .1s;transition:transform .4s,opacity .1s,-webkit-transform .4s;opacity:1}.md-search__inner{position:fixed;top:0;left:100%;width:100%;height:100%;-webkit-transform:translateX(5%);transform:translateX(5%);transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;opacity:0;z-index:2}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;-webkit-transform:translateX(0);transform:translateX(0);transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;opacity:1}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;-webkit-transform:translateX(-5%);transform:translateX(-5%)}.md-search__input{width:100%;height:2.4rem;font-size:.9rem}.md-search__icon[for=__search]{top:.6rem;left:.8rem}.md-search__icon[for=__search][for=__search]:before{content:"\E5C4"}[dir=rtl] .md-search__icon[for=__search][for=__search]:before{content:"\E5C8"}.md-search__icon[type=reset]{top:.6rem;right:.8rem}.md-search__output{top:2.4rem;bottom:0}.md-search-result__article--document:before{display:none}}@media only screen and (max-width:76.1875em){[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-header-nav__button.md-icon--home,.md-header-nav__button.md-logo{display:none}.md-hero__inner{margin-top:2.4rem;margin-bottom:1.2rem}.md-nav{background-color:#fff}.md-nav--primary,.md-nav--primary .md-nav{display:flex;position:absolute;top:0;right:0;left:0;flex-direction:column;height:100%;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}html .md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-weight:400;line-height:2.4rem;white-space:nowrap;cursor:pointer}html .md-nav--primary .md-nav__title:before{display:block;position:absolute;top:.2rem;left:.2rem;width:2rem;height:2rem;color:rgba(0,0,0,.54)}html .md-nav--primary .md-nav__title~.md-nav__list{background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07)}html .md-nav--primary .md-nav__title~.md-nav__list>.md-nav__item:first-child{border-top:0}html .md-nav--primary .md-nav__title--site{position:relative;background-color:#5700ff;color:#fff}html .md-nav--primary .md-nav__title--site .md-nav__button{display:block;position:absolute;top:.2rem;left:.2rem;width:3.2rem;height:3.2rem;font-size:2.4rem}html .md-nav--primary .md-nav__title--site:before{display:none}html [dir=rtl] .md-nav--primary .md-nav__title--site .md-nav__button,html [dir=rtl] .md-nav--primary .md-nav__title:before{right:.2rem;left:auto}.md-nav--primary .md-nav__list{flex:1;overflow-y:auto}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid rgba(0,0,0,.07)}[dir=rtl] .md-nav--primary .md-nav__item{padding:0}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E315"}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E314"}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link:after{position:absolute;top:50%;right:.6rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link:after{right:auto;left:.6rem}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:transparent}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav__toggle~.md-nav{display:flex;-webkit-transform:translateX(100%);transform:translateX(100%);transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);opacity:0}[dir=rtl] .md-nav__toggle~.md-nav{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.no-csstransforms3d .md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav{-webkit-transform:translateX(0);transform:translateX(0);transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1}.no-csstransforms3d .md-nav__toggle:checked~.md-nav{display:flex}.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;width:12.1rem;height:100%;-webkit-transform:translateX(0);transform:translateX(0);transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);background-color:#fff;z-index:3}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}.no-csstransforms3d .md-sidebar--primary{display:none}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);-webkit-transform:translateX(12.1rem);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{-webkit-transform:translateX(-12.1rem);transform:translateX(-12.1rem)}.no-csstransforms3d [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{display:block}.md-sidebar--primary .md-sidebar__scrollwrap{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;margin:0}.md-tabs{display:none}}@media only screen and (min-width:60em){.md-content{margin-right:12.1rem}[dir=rtl] .md-content{margin-right:0;margin-left:12.1rem}.md-header-nav__button.md-icon--search{display:none}.md-header-nav__source{display:block;width:11.5rem;max-width:11.5rem;padding-right:.6rem}[dir=rtl] .md-header-nav__source{padding-right:0;padding-left:.6rem}.md-search{padding:.2rem}.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);cursor:pointer}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:100%;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-search__inner{position:relative;width:11.5rem;margin-right:1rem;padding:.1rem 0;float:right;transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{margin-right:0;margin-left:1rem;float:left}.md-search__form,.md-search__input{border-radius:.1rem}.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);background-color:rgba(0,0,0,.26);color:inherit;font-size:.8rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:inherit}.md-search__input::-webkit-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{border-radius:.1rem .1rem 0 0;background-color:#fff;color:rgba(0,0,0,.87);text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:rgba(0,0,0,.54)}.md-search__output{top:1.9rem;transition:opacity .4s;opacity:0}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}.md-search__scrollwrap{max-height:0}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00e290}.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}.md-sidebar--secondary{display:block;margin-left:100%;-webkit-transform:translate(-100%);transform:translate(-100%)}[dir=rtl] .md-sidebar--secondary{margin-right:100%;margin-left:0;-webkit-transform:translate(100%);transform:translate(100%)}}@media only screen and (min-width:76.25em){.md-content{margin-left:12.1rem}[dir=rtl] .md-content{margin-right:12.1rem}.md-content__inner{margin-right:1.2rem;margin-left:1.2rem}.md-header-nav__button.md-icon--menu{display:none}.md-nav[data-md-state=animate]{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav__toggle~.md-nav{max-height:0;overflow:hidden}.no-js .md-nav__toggle~.md-nav{display:none}.md-nav[data-md-state=expand],.md-nav__toggle:checked~.md-nav{max-height:100%}.no-js .md-nav[data-md-state=expand],.no-js .md-nav__toggle:checked~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--nested>.md-nav__link:after{display:inline-block;-webkit-transform-origin:.45em .45em;transform-origin:.45em .45em;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;vertical-align:-.125em}.js .md-nav__item--nested>.md-nav__link:after{transition:-webkit-transform .4s;transition:transform .4s;transition:transform .4s,-webkit-transform .4s}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link:after{-webkit-transform:rotateX(180deg);transform:rotateX(180deg)}.md-search__inner{margin-right:1.4rem}[dir=rtl] .md-search__inner{margin-left:1.4rem}.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}.md-sidebar--secondary{margin-left:61rem}[dir=rtl] .md-sidebar--secondary{margin-right:61rem;margin-left:0}.md-tabs~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary .md-nav__title{display:block;padding:0}.md-tabs--active~.md-main .md-nav--primary .md-nav__title--site{display:none}.no-js .md-tabs--active~.md-main .md-nav--primary .md-nav{display:block}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{display:none;font-size:.7rem;overflow:auto;visibility:visible}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested>.md-nav__link{display:none}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--active{display:block}.md-tabs--active~.md-main .md-nav[data-md-level="1"]{max-height:none;overflow:visible}.md-tabs--active~.md-main .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:0}.md-tabs--active~.md-main .md-nav[data-md-level="1"] .md-nav .md-nav__title{display:none}}@media only screen and (min-width:45em){.md-footer-nav__link{width:50%}.md-footer-copyright{max-width:75%;float:left}[dir=rtl] .md-footer-copyright{float:right}.md-footer-social{padding:.6rem 0;float:right}[dir=rtl] .md-footer-social{float:left}}@media only screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(45);transform:scale(45)}}@media only screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(60);transform:scale(60)}}@media only screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(75);transform:scale(75)}}@media only screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}} \ No newline at end of file diff --git a/docs/theme/assets/stylesheets/extra.css b/docs/theme/assets/stylesheets/extra.css new file mode 100644 index 0000000000..788114b345 --- /dev/null +++ b/docs/theme/assets/stylesheets/extra.css @@ -0,0 +1,15 @@ +.help-sidetab { + position: fixed; + top: 70%; + right: -71px; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); + color: white; + background-color: #00e290; + transform: rotate(270deg); + padding: 5px 20px 5px 20px; + border-radius : 10px 10px 0px 0px; + cursor: pointer; + font-size: medium; +} \ No newline at end of file diff --git a/docs/theme/base.html b/docs/theme/base.html new file mode 100644 index 0000000000..8dc736895b --- /dev/null +++ b/docs/theme/base.html @@ -0,0 +1,220 @@ +{% import "partials/language.html" as lang with context %} +{% set feature = config.theme.feature %} +{% set palette = config.theme.palette %} +{% set font = config.theme.font %} + + + + {% block site_meta %} + + + + {% if page and page.meta and page.meta.description %} + + {% elif config.site_description %} + + {% endif %} + {% if page and page.meta and page.meta.redirect %} + + + + + {% elif page.canonical_url %} + + {% endif %} + {% if page and page.meta and page.meta.author %} + + {% elif config.site_author %} + + {% endif %} + {% for key in [ + "clipboard.copy", + "clipboard.copied", + "search.language", + "search.pipeline.stopwords", + "search.pipeline.trimmer", + "search.result.none", + "search.result.one", + "search.result.other", + "search.tokenizer" + ] %} + + {% endfor %} + + + {% endblock %} + {% block htmltitle %} + {% if page and page.meta and page.meta.title %} + {{ page.meta.title }} + {% elif page and page.title and not page.is_homepage %} + {{ page.title }} - {{ config.site_name }} + {% else %} + {{ config.site_name }} + {% endif %} + {% endblock %} + {% block styles %} + + {% if palette.primary or palette.accent %} + + {% endif %} + {% if palette.primary %} + {% import "partials/palette.html" as map %} + {% set primary = map.primary( + palette.primary | replace(" ", "-") | lower + ) %} + + {% endif %} + {% endblock %} + {% block libs %} + + {% endblock %} + {% block fonts %} + {% if font != false %} + + + + {% endif %} + {% endblock %} + + {% if config.extra.manifest %} + + {% endif %} + {% for path in config["extra_css"] %} + + {% endfor %} + {% block analytics %} + {% if config.google_analytics %} + {% include "partials/integrations/analytics.html" %} + {% endif %} + {% endblock %} + {% block extrahead %}{% endblock %} + + {% if palette.primary or palette.accent %} + {% set primary = palette.primary | replace(" ", "-") | lower %} + {% set accent = palette.accent | replace(" ", "-") | lower %} + + {% else %} + + {% endif %} + + + {% set platform = config.extra.repo_icon or config.repo_url %} + {% if "github" in platform %} + {% include "assets/images/icons/github.f0b8504a.svg" %} + {% elif "gitlab" in platform %} + {% include "assets/images/icons/gitlab.6dd19c00.svg" %} + {% elif "bitbucket" in platform %} + {% include "assets/images/icons/bitbucket.1b09e088.svg" %} + {% endif %} + + + + + + {% if page.toc | first is defined %} + + {{ lang.t('skip.link.title') }} + + {% endif %} + {% block header %} + {% include "partials/header.html" %} + {% endblock %} +
+ {% block hero %} + {% if page and page.meta and page.meta.hero %} + {% include "partials/hero.html" with context %} + {% endif %} + {% endblock %} + {% if feature.tabs %} + {% include "partials/tabs.html" %} + {% endif %} +
+
+ {% block site_nav %} + {% if nav %} +
+
+
+ {% include "partials/nav.html" %} +
+
+
+ {% endif %} + {% if page.toc %} +
+
+
+ {% include "partials/toc.html" %} +
+
+
+ {% endif %} + {% endblock %} +
+
+ {% block content %} + {% if page.edit_url %} + + {% endif %} + {% if not "\x3ch1" in page.content %} +

{{ page.title | default(config.site_name, true)}}

+ {% endif %} + {{ page.content }} + {% block source %} + {% if page and page.meta and page.meta.source %} +

{{ lang.t("meta.source") }}

+ {% set repo = config.repo_url %} + {% if repo | last == "/" %} + {% set repo = repo[:-1] %} + {% endif %} + {% set path = page.meta.path | default([""]) %} + {% set file = page.meta.source %} + + {{ file }} + + {% endif %} + {% endblock %} + {% endblock %} + {% block disqus %} + {% include "partials/integrations/disqus.html" %} + {% endblock %} +
+
+
+
+ {% block footer %} + {% include "partials/footer.html" %} + {% endblock %} +
+ {% block scripts %} + + {% if lang.t("search.language") != "en" %} + {% set languages = lang.t("search.language").split(",") %} + {% if languages | length and languages[0] != "" %} + {% set path = "assets/javascripts/lunr/" %} + + {% for language in languages | map("trim") %} + {% if language != "en" %} + {% if language == "ja" %} + + {% endif %} + {% if language in ("da", "de", "es", "fi", "fr", "hu", "it", "ja", "nl", "no", "pt", "ro", "ru", "sv", "th", "tr") %} + + {% endif %} + {% endif %} + {% endfor %} + {% if languages | length > 1 %} + + {% endif %} + {% endif %} + {% endif %} + + {% for path in config["extra_javascript"] %} + + {% endfor %} + {% endblock %} + + diff --git a/docs/theme/partials/footer.html b/docs/theme/partials/footer.html new file mode 100644 index 0000000000..35f12f0117 --- /dev/null +++ b/docs/theme/partials/footer.html @@ -0,0 +1,80 @@ +{% import "partials/language.html" as lang with context %} + +
+ +
diff --git a/docs/theme/partials/header.html b/docs/theme/partials/header.html new file mode 100644 index 0000000000..a063f3ad9c --- /dev/null +++ b/docs/theme/partials/header.html @@ -0,0 +1,87 @@ + + + +
+ + + +
diff --git a/docs/theme/partials/nav.html b/docs/theme/partials/nav.html new file mode 100644 index 0000000000..d68eb9d524 --- /dev/null +++ b/docs/theme/partials/nav.html @@ -0,0 +1,46 @@ + + + + diff --git a/eth/api.go b/eth/api.go index b3415f923c..6d8275df3b 100644 --- a/eth/api.go +++ b/eth/api.go @@ -61,6 +61,35 @@ func (api *PublicEthereumAPI) Coinbase() (common.Address, error) { return api.Etherbase() } +// Quorum +// StorageRoot returns the storage root of an account on the the given (optional) block height. +// If block number is not given the latest block is used. +func (s *PublicEthereumAPI) StorageRoot(addr common.Address, blockNr *rpc.BlockNumber) (common.Hash, error) { + var ( + pub, priv *state.StateDB + err error + ) + + if blockNr == nil || blockNr.Int64() == rpc.LatestBlockNumber.Int64() { + pub, priv, err = s.e.blockchain.State() + } else { + if ch := s.e.blockchain.GetHeaderByNumber(uint64(blockNr.Int64())); ch != nil { + pub, priv, err = s.e.blockchain.StateAt(ch.Root) + } else { + return common.Hash{}, fmt.Errorf("invalid block number") + } + } + + if err != nil { + return common.Hash{}, err + } + + if priv.Exist(addr) { + return priv.GetStorageRoot(addr) + } + return pub.GetStorageRoot(addr) +} + // Hashrate returns the POW hashrate func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { return hexutil.Uint64(api.e.Miner().HashRate()) @@ -277,14 +306,49 @@ func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI { } // DumpBlock retrieves the entire state of the database at a given block. -func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { +// Quorum adds an additional parameter to support private state dump +func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber, typ *string) (state.Dump, error) { + publicState, privateState, err := api.getStateDbsFromBlockNumber(blockNr) + if err != nil { + return state.Dump{}, err + } + + if typ != nil && *typ == "private" { + return privateState.RawDump(false, false, true), nil + } + return publicState.RawDump(false, false, true), nil +} + +// Quorum +// DumpAddress retrieves the state of an address at a given block. +// Quorum adds an additional parameter to support private state dump +func (api *PublicDebugAPI) DumpAddress(address common.Address, blockNr rpc.BlockNumber) (state.DumpAccount, error) { + publicState, privateState, err := api.getStateDbsFromBlockNumber(blockNr) + if err != nil { + return state.DumpAccount{}, err + } + + if accountDump, ok := privateState.DumpAddress(address); ok { + return accountDump, nil + } + if accountDump, ok := publicState.DumpAddress(address); ok { + return accountDump, nil + } + return state.DumpAccount{}, errors.New("error retrieving state") +} + +//Quorum +//Taken from DumpBlock, as it was reused in DumpAddress. +//Contains modifications from the original to return the private state db, as well as public. +func (api *PublicDebugAPI) getStateDbsFromBlockNumber(blockNr rpc.BlockNumber) (*state.StateDB, *state.StateDB, error) { if blockNr == rpc.PendingBlockNumber { // If we're dumping the pending state, we need to request // both the pending block as well as the pending state from // the miner and operate on those - _, stateDb := api.eth.miner.Pending() - return stateDb.RawDump(false, false, true), nil + _, publicState, privateState := api.eth.miner.Pending() + return publicState, privateState, nil } + var block *types.Block if blockNr == rpc.LatestBlockNumber { block = api.eth.blockchain.CurrentBlock() @@ -292,13 +356,9 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr)) } if block == nil { - return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) - } - stateDb, err := api.eth.BlockChain().StateAt(block.Root()) - if err != nil { - return state.Dump{}, err + return nil, nil, fmt.Errorf("block #%d not found", blockNr) } - return stateDb.RawDump(false, false, true), nil + return api.eth.BlockChain().StateAt(block.Root()) } // PrivateDebugAPI is the collection of Ethereum full node APIs exposed over @@ -364,7 +424,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta // If we're dumping the pending state, we need to request // both the pending block as well as the pending state from // the miner and operate on those - _, stateDb = api.eth.miner.Pending() + _, stateDb, _ = api.eth.miner.Pending() } else { var block *types.Block if number == rpc.LatestBlockNumber { @@ -375,7 +435,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta if block == nil { return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) } - stateDb, err = api.eth.BlockChain().StateAt(block.Root()) + stateDb, _, err = api.eth.BlockChain().StateAt(block.Root()) if err != nil { return state.IteratorDump{}, err } @@ -385,7 +445,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta if block == nil { return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) } - stateDb, err = api.eth.BlockChain().StateAt(block.Root()) + stateDb, _, err = api.eth.BlockChain().StateAt(block.Root()) if err != nil { return state.IteratorDump{}, err } @@ -412,7 +472,7 @@ type storageEntry struct { // StorageRangeAt returns the storage at the given block height and transaction index. func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { - _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0) + _, _, statedb, _, err := api.computeTxEnv(blockHash, txIndex, 0) if err != nil { return StorageRangeResult{}, err } @@ -505,7 +565,8 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) } - triedb := api.eth.BlockChain().StateCache().TrieDB() + statedb, _ := api.eth.BlockChain().StateCache() + triedb := statedb.TrieDB() oldTrie, err := trie.NewSecure(startBlock.Root(), triedb) if err != nil { diff --git a/eth/api_backend.go b/eth/api_backend.go index a7122da2cb..6de257a311 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -19,7 +19,9 @@ package eth import ( "context" "errors" + "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -33,8 +35,12 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/params" + pcore "github.com/ethereum/go-ethereum/permission/core" "github.com/ethereum/go-ethereum/rpc" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" ) // EthAPIBackend implements ethapi.Backend for full nodes @@ -42,6 +48,14 @@ type EthAPIBackend struct { extRPCEnabled bool eth *Ethereum gpo *gasprice.Oracle + + // Quorum + // + // hex node id from node public key + hexNodeId string + + // timeout value for call + evmCallTimeOut time.Duration } // ChainConfig returns the active chain configuration. @@ -95,6 +109,10 @@ func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*ty func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { // Pending block is only known by the miner if number == rpc.PendingBlockNumber { + if b.eth.protocolManager.raftMode { + // Use latest instead. + return b.eth.blockchain.CurrentBlock(), nil + } block := b.eth.miner.PendingBlock() return block, nil } @@ -130,11 +148,21 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r return nil, errors.New("invalid arguments; neither block nor hash specified") } -func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { +func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (vm.MinimalApiState, *types.Header, error) { // Pending state is only known by the miner if number == rpc.PendingBlockNumber { - block, state := b.eth.miner.Pending() - return state, block.Header(), nil + // Quorum + if b.eth.protocolManager.raftMode { + // Use latest instead. + header, err := b.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if header == nil || err != nil { + return nil, nil, err + } + publicState, privateState, err := b.eth.BlockChain().StateAt(header.Root) + return EthAPIState{publicState, privateState}, header, err + } + block, publicState, privateState := b.eth.miner.Pending() + return EthAPIState{publicState, privateState}, block.Header(), nil } // Otherwise resolve the block number and return its state header, err := b.HeaderByNumber(ctx, number) @@ -144,11 +172,12 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B if header == nil { return nil, nil, errors.New("header not found") } - stateDb, err := b.eth.BlockChain().StateAt(header.Root) - return stateDb, header, err + stateDb, privateState, err := b.eth.BlockChain().StateAt(header.Root) + return EthAPIState{stateDb, privateState}, header, err + } -func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { +func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.MinimalApiState, *types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) } @@ -163,8 +192,9 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, nil, errors.New("hash is not currently canonical") } - stateDb, err := b.eth.BlockChain().StateAt(header.Root) - return stateDb, header, err + stateDb, privateState, err := b.eth.BlockChain().StateAt(header.Root) + return EthAPIState{stateDb, privateState}, header, err + } return nil, nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -189,11 +219,27 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(hash) } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state vm.MinimalApiState, header *types.Header) (*vm.EVM, func() error, error) { + statedb := state.(EthAPIState) vmError := func() error { return nil } - context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) - return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil + evmCtx := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) + if _, ok := b.SupportsMultitenancy(ctx); ok { + evmCtx = core.NewMultitenancyAwareEVMContext(ctx, evmCtx) + } + + // Set the private state to public state if contract address is not present in the private state + to := common.Address{} + if msg.To() != nil { + to = *msg.To() + } + + privateState := statedb.privateState + if !privateState.Exist(to) { + privateState = statedb.state + } + + return vm.NewEVM(evmCtx, statedb.state, privateState, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { @@ -221,6 +267,11 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri } func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + // validation for node need to happen here and cannot be done as a part of + // validateTx in tx_pool.go as tx_pool validation will happen in every node + if b.hexNodeId != "" && !pcore.ValidateNodeForTxn(b.hexNodeId, signedTx.From()) { + return errors.New("cannot send transaction from this node") + } return b.eth.txPool.AddLocal(signedTx) } @@ -270,7 +321,11 @@ func (b *EthAPIBackend) ProtocolVersion() int { } func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { - return b.gpo.SuggestPrice(ctx) + if b.ChainConfig().IsQuorum { + return big.NewInt(0), nil + } else { + return b.gpo.SuggestPrice(ctx) + } } func (b *EthAPIBackend) ChainDb() ethdb.Database { @@ -289,6 +344,10 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +func (b *EthAPIBackend) CallTimeOut() time.Duration { + return b.evmCallTimeOut +} + func (b *EthAPIBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } @@ -307,3 +366,159 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +// The validation of pre-requisite for multitenancy is done when EthService +// is being created. So it's safe to use the config value here. +func (b *EthAPIBackend) SupportsMultitenancy(rpcCtx context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) { + authToken, isPreauthenticated := rpcCtx.Value(rpc.CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken) + if isPreauthenticated && b.eth.config.EnableMultitenancy { + return authToken, true + } + return nil, false +} + +func (b *EthAPIBackend) AccountExtraDataStateGetterByNumber(ctx context.Context, number rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) { + s, _, err := b.StateAndHeaderByNumber(ctx, number) + return s, err +} + +func (b *EthAPIBackend) IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*multitenancy.ContractSecurityAttribute) (bool, error) { + auth, err := b.eth.contractAuthzProvider.IsAuthorized(ctx, authToken, attributes...) + if err != nil { + log.Error("failed to perform authorization check", "err", err, "granted", string(authToken.RawToken), "ask", attributes) + return false, err + } + return auth, nil +} + +// used by Quorum +type EthAPIState struct { + state, privateState *state.StateDB +} + +func (s EthAPIState) GetBalance(addr common.Address) *big.Int { + if s.privateState.Exist(addr) { + return s.privateState.GetBalance(addr) + } + return s.state.GetBalance(addr) +} + +func (s EthAPIState) GetCode(addr common.Address) []byte { + if s.privateState.Exist(addr) { + return s.privateState.GetCode(addr) + } + return s.state.GetCode(addr) +} + +func (s EthAPIState) SetNonce(addr common.Address, nonce uint64) { + if s.privateState.Exist(addr) { + s.privateState.SetNonce(addr, nonce) + } else { + s.state.SetNonce(addr, nonce) + } +} + +func (s EthAPIState) SetCode(addr common.Address, code []byte) { + if s.privateState.Exist(addr) { + s.privateState.SetCode(addr, code) + } else { + s.state.SetCode(addr, code) + } +} + +func (s EthAPIState) SetBalance(addr common.Address, balance *big.Int) { + if s.privateState.Exist(addr) { + s.privateState.SetBalance(addr, balance) + } else { + s.state.SetBalance(addr, balance) + } +} + +func (s EthAPIState) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { + if s.privateState.Exist(addr) { + s.privateState.SetStorage(addr, storage) + } else { + s.state.SetStorage(addr, storage) + } +} + +func (s EthAPIState) SetState(a common.Address, key common.Hash, value common.Hash) { + if s.privateState.Exist(a) { + s.privateState.SetState(a, key, value) + } else { + s.state.SetState(a, key, value) + } +} + +func (s EthAPIState) GetState(a common.Address, b common.Hash) common.Hash { + if s.privateState.Exist(a) { + return s.privateState.GetState(a, b) + } + return s.state.GetState(a, b) +} + +func (s EthAPIState) GetNonce(addr common.Address) uint64 { + if s.privateState.Exist(addr) { + return s.privateState.GetNonce(addr) + } + return s.state.GetNonce(addr) +} + +func (s EthAPIState) GetPrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + if s.privateState.Exist(addr) { + return s.privateState.GetPrivacyMetadata(addr) + } + return nil, fmt.Errorf("%x: %w", addr, common.ErrNotPrivateContract) +} + +func (s EthAPIState) GetManagedParties(addr common.Address) ([]string, error) { + if s.privateState.Exist(addr) { + return s.privateState.GetManagedParties(addr) + } + return nil, fmt.Errorf("%x: %w", addr, common.ErrNotPrivateContract) +} + +func (s EthAPIState) GetRLPEncodedStateObject(addr common.Address) ([]byte, error) { + getFunc := s.state.GetRLPEncodedStateObject + if s.privateState.Exist(addr) { + getFunc = s.privateState.GetRLPEncodedStateObject + } + return getFunc(addr) +} + +func (s EthAPIState) GetProof(addr common.Address) ([][]byte, error) { + if s.privateState.Exist(addr) { + return s.privateState.GetProof(addr) + } + return s.state.GetProof(addr) +} + +func (s EthAPIState) GetStorageProof(addr common.Address, h common.Hash) ([][]byte, error) { + if s.privateState.Exist(addr) { + return s.privateState.GetStorageProof(addr, h) + } + return s.state.GetStorageProof(addr, h) +} + +func (s EthAPIState) StorageTrie(addr common.Address) state.Trie { + if s.privateState.Exist(addr) { + return s.privateState.StorageTrie(addr) + } + return s.state.StorageTrie(addr) +} + +func (s EthAPIState) Error() error { + if s.privateState.Error() != nil { + return s.privateState.Error() + } + return s.state.Error() +} + +func (s EthAPIState) GetCodeHash(addr common.Address) common.Hash { + if s.privateState.Exist(addr) { + return s.privateState.GetCodeHash(addr) + } + return s.state.GetCodeHash(addr) +} + +//func (s MinimalApiState) Error diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 125c6fd70c..0c97f15dd2 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -78,10 +78,11 @@ type txTraceResult struct { // blockTraceTask represents a single block trace task when an entire chain is // being traced. type blockTraceTask struct { - statedb *state.StateDB // Intermediate state prepped for tracing - block *types.Block // Block to trace the transactions from - rootref common.Hash // Trie root reference held for this task - results []*txTraceResult // Trace results procudes by the task + statedb *state.StateDB // Intermediate state prepped for tracing + privateStateDb *state.StateDB // Quorum + block *types.Block // Block to trace the transactions from + rootref common.Hash // Trie root reference held for this task + results []*txTraceResult // Trace results procudes by the task } // blockTraceResult represets the results of tracing a single block when an entire @@ -95,8 +96,9 @@ type blockTraceResult struct { // txTraceTask represents a single transaction trace task when an entire block // is being traced. type txTraceTask struct { - statedb *state.StateDB // Intermediate state prepped for tracing - index int // Transaction offset in the block + statedb *state.StateDB // Intermediate state prepped for tracing + privateStateDb *state.StateDB + index int // Transaction offset in the block } // TraceChain returns the structured logs created during the execution of EVM @@ -148,6 +150,9 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Ensure we have a valid starting state before doing any work origin := start.NumberU64() database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) // Chain tracing will probably start at genesis + // Quorum + privateDatabase := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) + // End Quorum if number := start.NumberU64(); number > 0 { start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) @@ -155,7 +160,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl return nil, fmt.Errorf("parent block #%d not found", number-1) } } - statedb, err := state.New(start.Root(), database, nil) + statedb, privateStateDb, err := state.NewDual(start.Root(), database, nil, api.eth.chainDb, privateDatabase, nil) if err != nil { // If the starting state is missing, allow some number of blocks to be reexecuted reexec := defaultTraceReexec @@ -168,7 +173,9 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl if start == nil { break } - if statedb, err = state.New(start.Root(), database, nil); err == nil { + + // Quorum - use NewDual(...) to create private state too + if statedb, privateStateDb, err = state.NewDual(start.Root(), database, nil, api.eth.chainDb, privateDatabase, nil); err == nil { break } } @@ -208,7 +215,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl msg, _ := tx.AsMessage(signer) vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil) - res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) + res, err := api.traceTx(ctx, msg, tx, vmctx, task.statedb, task.privateStateDb, config) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -216,6 +223,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number())) + task.privateStateDb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number())) task.results[i] = &txTraceResult{Result: res} } // Stream the result back to the user or abort on teardown @@ -282,14 +290,14 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl txs := block.Transactions() select { - case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}: + case tasks <- &blockTraceTask{statedb: statedb.Copy(), privateStateDb: privateStateDb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}: case <-notifier.Closed(): return } traced += uint64(len(txs)) } // Generate the next state snapshot fast without tracing - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + _, _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, privateStateDb, vm.Config{}) if err != nil { failed = err break @@ -304,6 +312,16 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl failed = err break } + + privateStateRoot, err := privateStateDb.Commit(true) + if err != nil { + failed = err + break + } + if err := privateStateDb.Reset(privateStateRoot); err != nil { + failed = err + break + } // Reference the trie twice, once for us, once for the tracer database.TrieDB().Reference(root, common.Hash{}) if number >= origin { @@ -454,7 +472,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.computeStateDB(parent, reexec) + statedb, privateStateDb, err := api.computeStateDB(parent, reexec) if err != nil { return nil, err } @@ -479,10 +497,11 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // Fetch and execute the next transaction trace tasks for task := range jobs { - msg, _ := txs[task.index].AsMessage(signer) + txn := txs[task.index] + msg, _ := txn.AsMessage(signer) vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) - res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) + res, err := api.traceTx(ctx, msg, txn, vmctx, task.statedb, task.privateStateDb, config) if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue @@ -495,13 +514,22 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, var failed error for i, tx := range txs { // Send the trace task over for execution - jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} + jobs <- &txTraceTask{ + statedb: statedb.Copy(), + privateStateDb: privateStateDb.Copy(), + index: i, + } // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer) vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) + // Quorum + privateStateDbToUse := core.PrivateStateDBForTxn(api.eth.blockchain.Config().IsQuorum, tx.IsPrivate(), statedb, privateStateDb) + vmenv := vm.NewEVM(vmctx, statedb, privateStateDbToUse, api.eth.blockchain.Config(), vm.Config{}) + vmenv.SetCurrentTX(tx) + // /Quorum + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -509,6 +537,8 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // Finalize the state so any modifications are written to the trie // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + privateStateDb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } close(jobs) pend.Wait() @@ -542,7 +572,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.computeStateDB(parent, reexec) + statedb, privateStateDb, err := api.computeStateDB(parent, reexec) if err != nil { return nil, err } @@ -595,7 +625,12 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } } // Execute the transaction and flush any traces to disk - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) + // Quorum + privateStateDbToUse := core.PrivateStateDBForTxn(api.eth.blockchain.Config().IsQuorum, tx.IsPrivate(), statedb, privateStateDb) + vmenv := vm.NewEVM(vmctx, statedb, privateStateDbToUse, api.eth.blockchain.Config(), vmConf) + vmenv.SetCurrentTX(tx) + // /Quorum + _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -633,31 +668,36 @@ func containsTx(block *types.Block, hash common.Hash) bool { // computeStateDB retrieves the state database associated with a certain block. // If no state is locally available for the given block, a number of blocks are // attempted to be reexecuted to generate the desired state. -func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) { +func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, *state.StateDB, error) { // If we have the state fully available, use that - statedb, err := api.eth.blockchain.StateAt(block.Root()) + statedb, privateStateDb, err := api.eth.blockchain.StateAt(block.Root()) if err == nil { - return statedb, nil + return statedb, privateStateDb, nil } // Otherwise try to reexec blocks until we find a state or reach our limit origin := block.NumberU64() database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) + // Quorum + privateDatabase := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) + // End Quorum for i := uint64(0); i < reexec; i++ { block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if block == nil { break } - if statedb, err = state.New(block.Root(), database, nil); err == nil { + + // Quorum - use NewDual(...) to create private state too + if statedb, privateStateDb, err = state.NewDual(block.Root(), database, nil, api.eth.chainDb, privateDatabase, nil); err == nil { break } } if err != nil { switch err.(type) { case *trie.MissingNodeError: - return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) + return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) default: - return nil, err + return nil, nil, err } } // State was available at historical point, regenerate @@ -674,19 +714,26 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* } // Retrieve the next block to regenerate and process it if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { - return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) + return nil, nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) } - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + _, _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, privateStateDb, vm.Config{}) if err != nil { - return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) + return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) } // Finalize the state so any modifications are written to the trie root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) if err != nil { - return nil, err + return nil, nil, err } if err := statedb.Reset(root); err != nil { - return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + } + privateStateRoot, err := privateStateDb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) + if err != nil { + return nil, nil, err + } + if err := privateStateDb.Reset(privateStateRoot); err != nil { + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) } database.TrieDB().Reference(root, common.Hash{}) if proot != (common.Hash{}) { @@ -696,7 +743,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* } nodes, imgs := database.TrieDB().Size() log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) - return statedb, nil + return statedb, privateStateDb, nil } // TraceTransaction returns the structured logs created during the execution of EVM @@ -711,18 +758,18 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha if config != nil && config.Reexec != nil { reexec = *config.Reexec } - msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec) + msg, vmctx, statedb, privateStateDb, err := api.computeTxEnv(blockHash, int(index), reexec) if err != nil { return nil, err } // Trace the transaction and return - return api.traceTx(ctx, msg, vmctx, statedb, config) + return api.traceTx(ctx, msg, tx, vmctx, statedb, privateStateDb, config) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, tx *types.Transaction, vmctx vm.Context, statedb *state.StateDB, privateStateDb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -755,8 +802,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v default: tracer = vm.NewStructLogger(config.LogConfig) } + + // Quorum + // Set the private state to public state if it is not a private message + isPrivate := false + if msg, ok := message.(core.PrivateMessage); ok && msg.IsPrivate() { + isPrivate = true + } + privateStateDbToUse := core.PrivateStateDBForTxn(api.eth.blockchain.Config().IsQuorum, isPrivate, statedb, privateStateDb) + // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, statedb, privateStateDbToUse, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv.SetCurrentTX(tx) + // /Quorum result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { @@ -786,43 +844,47 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } // computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { +func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, *state.StateDB, error) { // Create the parent state database block := api.eth.blockchain.GetBlockByHash(blockHash) if block == nil { - return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash) + return nil, vm.Context{}, nil, nil, fmt.Errorf("block %#x not found", blockHash) } parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + return nil, vm.Context{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) } - statedb, err := api.computeStateDB(parent, reexec) + statedb, privateStateDb, err := api.computeStateDB(parent, reexec) if err != nil { - return nil, vm.Context{}, nil, err + return nil, vm.Context{}, nil, nil, err } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.Context{}, statedb, nil + return nil, vm.Context{}, statedb, privateStateDb, nil } // Recompute transactions up to the target index. signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number()) for idx, tx := range block.Transactions() { + // Quorum + privateStateDbToUse := core.PrivateStateDBForTxn(api.eth.blockchain.Config().IsQuorum, tx.IsPrivate(), statedb, privateStateDb) + // /Quorum // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) if idx == txIndex { - return msg, context, statedb, nil + return msg, context, statedb, privateStateDb, nil } // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, statedb, privateStateDbToUse, api.eth.blockchain.Config(), vm.Config{}) + vmenv.SetCurrentTX(tx) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.Context{}, nil, nil, fmt.Errorf("tx %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash) + return nil, vm.Context{}, nil, nil, fmt.Errorf("tx index %d out of range for block %#x", txIndex, blockHash) } diff --git a/eth/backend.go b/eth/backend.go index e4f98360db..a45b8a74e6 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -32,11 +32,14 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/istanbul" + istanbulBackend "github.com/ethereum/go-ethereum/consensus/istanbul/backend" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -45,6 +48,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -95,6 +99,10 @@ type Ethereum struct { netRPCService *ethapi.PublicNetAPI lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) + + // Quorum - Multitenancy + // contractAuthzProvider is set after node starts instead in New() + contractAuthzProvider multitenancy.ContractAuthorizationProvider } func (s *Ethereum) AddLesServer(ls LesServer) { @@ -110,6 +118,13 @@ func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) { } } +// Quorum +// +// Set the decision manager for multitenancy support +func (s *Ethereum) SetContractAuthorizationProvider(dm multitenancy.ContractAuthorizationProvider) { + s.contractAuthzProvider = dm +} + // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { @@ -146,12 +161,25 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } log.Info("Initialised chain configuration", "config", chainConfig) + // changes to manipulate the chain id for migration from 2.0.2 and below version to 2.0.3 + // version of Quorum - this is applicable for v2.0.3 onwards + if chainConfig.IsQuorum { + if (chainConfig.ChainID != nil && chainConfig.ChainID.Int64() == 1) || config.NetworkId == 1 { + return nil, errors.New("Cannot have chain id or network id as 1.") + } + } + + if !rawdb.GetIsQuorumEIP155Activated(chainDb) && chainConfig.ChainID != nil { + //Upon starting the node, write the flag to disallow changing ChainID/EIP155 block after HF + rawdb.WriteQuorumEIP155Activation(chainDb) + } + eth := &Ethereum{ config: config, chainDb: chainDb, eventMux: ctx.EventMux, accountManager: ctx.AccountManager, - engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + engine: CreateConsensusEngine(ctx, chainConfig, config, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -160,12 +188,35 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), } + // Quorum: Set protocol Name/Version + // keep `var protocolName = "eth"` as is, and only update the quorum consensus specific protocol + // This is used to enable the eth service to return multiple devp2p subprotocols. + // Previously, for istanbul/64 istnbul/99 and clique (v2.6) `protocolName` would be overridden and + // set to the consensus subprotocol name instead of "eth", meaning the node would no longer + // communicate over the "eth" subprotocol, e.g. "eth" or "istanbul/99" but not eth" and "istanbul/99". + // With this change, support is added so that the "eth" subprotocol remains and optionally a consensus subprotocol + // can be added allowing the node to communicate over "eth" and an optional consensus subprotocol, e.g. "eth" and "istanbul/100" + if chainConfig.IsQuorum { + quorumProtocol := eth.engine.Protocol() + // set the quorum specific consensus devp2p subprotocol, eth subprotocol remains set to protocolName as in upstream geth. + quorumConsensusProtocolName = quorumProtocol.Name + quorumConsensusProtocolVersions = quorumProtocol.Versions + quorumConsensusProtocolLengths = quorumProtocol.Lengths + } + + // force to set the istanbul etherbase to node key address + if chainConfig.Istanbul != nil { + eth.etherbase = crypto.PubkeyToAddress(ctx.NodeKey().PublicKey) + } bcVersion := rawdb.ReadDatabaseVersion(chainDb) var dbVer = "" if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } - log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer) + log.Info("Initialising Ethereum protocol", "name", protocolName, "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer) + if chainConfig.IsQuorum { + log.Info("Initialising Quorum consensus protocol", "name", quorumConsensusProtocolName, "versions", quorumConsensusProtocolVersions, "network", config.NetworkId, "dbversion", dbVer) + } if !config.SkipBcVersionCheck { if bcVersion != nil && *bcVersion > core.BlockChainVersion { @@ -190,7 +241,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { SnapshotLimit: config.SnapshotCache, } ) - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) + newBlockChainFunc := core.NewBlockChain + if config.EnableMultitenancy { + newBlockChainFunc = core.NewMultitenantBlockChain + } + eth.blockchain, err = newBlockChainFunc(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) if err != nil { return nil, err } @@ -213,13 +268,14 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if checkpoint == nil { checkpoint = params.TrustedCheckpoints[genesisHash] } - if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil { + if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist, config.RaftMode); err != nil { return nil, err } eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) - eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) + eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData, eth.blockchain.Config().IsQuorum)) - eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} + hexNodeId := fmt.Sprintf("%x", crypto.FromECDSAPub(&ctx.NodeKey().PublicKey)[1:]) // Quorum + eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil, hexNodeId, config.EVMCallTimeOut} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice @@ -234,7 +290,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return eth, nil } -func makeExtraData(extra []byte) []byte { +func makeExtraData(extra []byte, isQuorum bool) []byte { if len(extra) == 0 { // create default extradata extra, _ = rlp.EncodeToBytes([]interface{}{ @@ -244,21 +300,34 @@ func makeExtraData(extra []byte) []byte { runtime.GOOS, }) } - if uint64(len(extra)) > params.MaximumExtraDataSize { - log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize) + if uint64(len(extra)) > params.GetMaximumExtraDataSize(isQuorum) { + log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.GetMaximumExtraDataSize(isQuorum)) extra = nil } return extra } // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { +func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { // If proof-of-authority is requested, set it up if chainConfig.Clique != nil { + chainConfig.Clique.AllowedFutureBlockTime = config.Miner.AllowedFutureBlockTime //Quorum return clique.New(chainConfig.Clique, db) } + // If Istanbul is requested, set it up + if chainConfig.Istanbul != nil { + if chainConfig.Istanbul.Epoch != 0 { + config.Istanbul.Epoch = chainConfig.Istanbul.Epoch + } + config.Istanbul.ProposerPolicy = istanbul.ProposerPolicy(chainConfig.Istanbul.ProposerPolicy) + config.Istanbul.Ceil2Nby3Block = chainConfig.Istanbul.Ceil2Nby3Block + config.Istanbul.AllowedFutureBlockTime = config.Miner.AllowedFutureBlockTime //Quorum + + return istanbulBackend.New(&config.Istanbul, ctx.NodeKey(), db) + } + // Otherwise assume proof-of-work - switch config.PowMode { + switch config.Ethash.PowMode { case ethash.ModeFake: log.Warn("Ethash used in fake mode") return ethash.NewFaker() @@ -269,18 +338,11 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo log.Warn("Ethash used in shared mode") return ethash.NewShared() default: - engine := ethash.New(ethash.Config{ - CacheDir: ctx.ResolvePath(config.CacheDir), - CachesInMem: config.CachesInMem, - CachesOnDisk: config.CachesOnDisk, - CachesLockMmap: config.CachesLockMmap, - DatasetDir: config.DatasetDir, - DatasetsInMem: config.DatasetsInMem, - DatasetsOnDisk: config.DatasetsOnDisk, - DatasetsLockMmap: config.DatasetsLockMmap, - }, notify, noverify) - engine.SetThreads(-1) // Disable CPU mining - return engine + // For Quorum, Raft run as a separate service, so + // the Ethereum service still needs a consensus engine, + // use the consensus with the lightest overhead + log.Warn("Ethash used in full fake mode") + return ethash.NewFullFaker() } } @@ -302,7 +364,7 @@ func (s *Ethereum) APIs() []rpc.API { } // Append all the local APIs and return - return append(apis, []rpc.API{ + apis = append(apis, []rpc.API{ { Namespace: "eth", Version: "1.0", @@ -348,6 +410,7 @@ func (s *Ethereum) APIs() []rpc.API { Public: true, }, }...) + return apis } func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { @@ -434,8 +497,12 @@ func (s *Ethereum) shouldPreserve(block *types.Block) bool { // SetEtherbase sets the mining reward address. func (s *Ethereum) SetEtherbase(etherbase common.Address) { s.lock.Lock() + defer s.lock.Unlock() + if _, ok := s.engine.(consensus.Istanbul); ok { + log.Error("Cannot set etherbase in Istanbul consensus") + return + } s.etherbase = etherbase - s.lock.Unlock() s.miner.SetEtherbase(etherbase) } @@ -516,6 +583,17 @@ func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManage func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } +// Quorum +// adds quorum specific protocols to the Protocols() function which in the associated upstream geth version returns +// only one subprotocol, "eth", and the supported versions of the "eth" protocol. +// Quorum uses the eth service to run configurable consensus protocols, e.g. istanbul. Thru release v20.10.0 +// the "eth" subprotocol would be replaced with a modified subprotocol, e.g. "istanbul/99" which would contain all the "eth" +// messages + the istanbul message and be communicated over the consensus specific subprotocol ("istanbul"), and +// not over "eth". +// Now the eth service supports multiple protocols, e.g. "eth" and an optional consensus +// protocol, e.g. "istanbul/100". +// /Quorum + // Protocols implements node.Service, returning all the currently configured // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { @@ -528,6 +606,15 @@ func (s *Ethereum) Protocols() []p2p.Protocol { if s.lesServer != nil { protos = append(protos, s.lesServer.Protocols()...) } + + // /Quorum + // add additional quorum consensus protocol if set and if not set to "eth", e.g. istanbul + if quorumConsensusProtocolName != "" && quorumConsensusProtocolName != protocolName { + quorumProtos := s.quorumConsensusProtocols() + protos = append(protos, quorumProtos...) + } + // /end Quorum + return protos } @@ -578,3 +665,7 @@ func (s *Ethereum) Stop() error { s.eventMux.Stop() return nil } + +func (s *Ethereum) CalcGasLimit(block *types.Block) uint64 { + return core.CalcGasLimit(block, s.config.Miner.GasFloor, s.config.Miner.GasCeil) +} diff --git a/eth/bloombits.go b/eth/bloombits.go index f8b77f9cff..2bc3df30ed 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -115,10 +115,14 @@ func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHea return err } -// Process implements core.ChainIndexerBackend, adding a new header's bloom into -// the index. +// Process implements core.ChainIndexerBackend, executes an Or operation on header.bloom and private bloom +// (header.bloom | private bloom) and adds to index func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error { - b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom) + publicBloom := header.Bloom + privateBloom := rawdb.GetPrivateBlockBloom(b.db, header.Number.Uint64()) + publicBloom.OrBloom(privateBloom.Bytes()) + + b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), publicBloom) b.head = header.Hash() return nil } diff --git a/eth/config.go b/eth/config.go index 8b9651e09b..29c74c4cf8 100644 --- a/eth/config.go +++ b/eth/config.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/istanbul" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -57,17 +58,17 @@ var DefaultConfig = Config{ DatasetsOnDisk: 2, DatasetsLockMmap: false, }, - NetworkId: 1, + NetworkId: 1337, LightPeers: 100, UltraLightFraction: 75, - DatabaseCache: 512, + DatabaseCache: 768, TrieCleanCache: 256, TrieDirtyCache: 256, TrieTimeout: 60 * time.Minute, SnapshotCache: 256, Miner: miner.Config{ - GasFloor: 8000000, - GasCeil: 8000000, + GasFloor: params.MinGasLimit, + GasCeil: params.GenesisGasLimit, GasPrice: big.NewInt(params.GWei), Recommit: 3 * time.Second, }, @@ -75,6 +76,8 @@ var DefaultConfig = Config{ RPCGasCap: 25000000, GPO: DefaultFullGPOConfig, RPCTxFeeCap: 1, // 1 ether + + Istanbul: *istanbul.DefaultConfig, // Quorum } func init() { @@ -159,6 +162,11 @@ type Config struct { // Enables tracking of SHA3 preimages in the VM EnablePreimageRecording bool + RaftMode bool + EnableNodePermission bool + // Istanbul options + Istanbul istanbul.Config + // Miscellaneous options DocRoot string `toml:"-"` @@ -180,4 +188,11 @@ type Config struct { // CheckpointOracle is the configuration for checkpoint oracle. CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + + // Quorum + // timeout value for call + EVMCallTimeOut time.Duration + + // Quorum + EnableMultitenancy bool } diff --git a/eth/config_test.go b/eth/config_test.go new file mode 100644 index 0000000000..b50ae1b25c --- /dev/null +++ b/eth/config_test.go @@ -0,0 +1,21 @@ +package eth + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestQuorumDefautConfig(t *testing.T) { + type data struct { + actual uint64 + expected uint64 + } + var testData = map[string]data{ + "eth.DefaultConfig.Miner.GasFloor": {DefaultConfig.Miner.GasFloor, 700000000}, + "eth.DefaultConfig.Miner.GasCeil": {DefaultConfig.Miner.GasCeil, 800000000}, + } + for k, v := range testData { + assert.Equal(t, v.expected, v.actual, k+" value mismatch") + } +} diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 3a289a9c7f..5776a86a5c 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/permission/core" "github.com/ethereum/go-ethereum/trie" ) @@ -86,6 +87,8 @@ var ( errInvalidBody = errors.New("retrieved block body is invalid") errInvalidReceipt = errors.New("retrieved receipt is invalid") errCancelStateFetch = errors.New("state data download canceled (requested)") + errCancelHeaderFetch = errors.New("block header download canceled (requested)") + errCancelBlockFetch = errors.New("block download canceled (requested)") errCancelContentProcessing = errors.New("content processing canceled (requested)") errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") @@ -211,6 +214,11 @@ type BlockChain interface { // New creates a new downloader to fetch hashes and blocks from remote peers. func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn) *Downloader { + // Quorum + // reset the value of maxForkAncenstry for Quorum based + fullMaxForkAncestry = uint64(params.GetImmutabilityThreshold()) + // End Quorum + if lightchain == nil { lightchain = chain } @@ -357,6 +365,11 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode if !atomic.CompareAndSwapInt32(&d.synchronising, 0, 1) { return errBusy } + + // Quorum + // changes for permissions. added set sync status to indicate permissions that node sync has started + core.SetSyncStatus() + defer atomic.StoreInt32(&d.synchronising, 0) // Post a user notification of the sync (only once per session) @@ -411,6 +424,9 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode if p == nil { return errUnknownPeer } + if mode == BoundedFullSync { + return d.syncWithPeerUntil(p, hash, td) + } return d.syncWithPeer(p, hash, td) } @@ -1498,7 +1514,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er } } // Unless we're doing light chains, schedule the headers for associated content retrieval - if mode == FullSync || mode == FastSync { + if mode == FullSync || mode == FastSync || mode == BoundedFullSync { // If we've reached the allowed number of pending headers, stall a bit for d.queue.PendingBlocks() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { select { @@ -1870,3 +1886,229 @@ func (d *Downloader) requestTTL() time.Duration { } return ttl } + +// Extra downloader functionality for non-proof-of-work consensus + +// Synchronizes with a peer, but only up to the provided Hash +func (d *Downloader) syncWithPeerUntil(p *peerConnection, hash common.Hash, td *big.Int) (err error) { + d.mux.Post(StartEvent{}) + defer func() { + // reset on error + if err != nil { + d.mux.Post(FailedEvent{err}) + } else { + // Raft syncWithPeerUntil never use the latest field in DoneEvent + // therefore post empty DoneEvent only + d.mux.Post(DoneEvent{}) + } + }() + if p.version < 62 { + return errTooOld + } + + log.Info("Synchronising with the network", "id", p.id, "version", p.version) + defer func(start time.Time) { + log.Info("Synchronisation terminated", "duration", time.Since(start)) + }(time.Now()) + + frozen, _ := d.stateDB.Ancients() + localHeight := d.blockchain.CurrentBlock().NumberU64() + + // check if recovering state db and only ancient db is present + // in this case its possible that local hash is not the latest + // as per raft wal. change header to remote header + var remoteHeader *types.Header + if localHeight == 0 && frozen > 0 { + // statedb was removed and is being recovered now + // we trust the peer height and sync upto that + remoteHeader, err = d.fetchHeight(p) + } else { + remoteHeader, err = d.fetchHeader(p, hash) + } + if err != nil { + return err + } + + remoteHeight := remoteHeader.Number.Uint64() + + d.syncStatsLock.Lock() + if d.syncStatsChainHeight <= localHeight || d.syncStatsChainOrigin > localHeight { + d.syncStatsChainOrigin = localHeight + } + d.syncStatsChainHeight = remoteHeight + d.syncStatsLock.Unlock() + + d.queue.Prepare(localHeight+1, d.getMode()) + if d.syncInitHook != nil { + d.syncInitHook(localHeight, remoteHeight) + } + + pivot := uint64(0) + + fetchers := []func() error{ + func() error { return d.fetchBoundedHeaders(p, localHeight+1, remoteHeight) }, + func() error { return d.fetchBodies(localHeight + 1) }, + func() error { return d.fetchReceipts(localHeight + 1) }, // Receipts are only retrieved during fast sync + func() error { return d.processHeaders(localHeight+1, pivot, td) }, + d.processFullSyncContent, //This must be added to clear the buffer of downloaded content as it's being filled + } + return d.spawnSync(fetchers) +} + +// Fetches a single header from a peer +func (d *Downloader) fetchHeader(p *peerConnection, hash common.Hash) (*types.Header, error) { + log.Info("retrieving remote chain height", "peer", p) + + go p.peer.RequestHeadersByHash(hash, 1, 0, false) + + timeout := time.After(d.requestTTL()) + for { + select { + case <-d.cancelCh: + return nil, errCancelBlockFetch + + case packet := <-d.headerCh: + // Discard anything not from the origin peer + if packet.PeerId() != p.id { + log.Info("Received headers from incorrect peer", "peer id", packet.PeerId()) + break + } + // Make sure the peer actually gave something valid + headers := packet.(*headerPack).headers + if len(headers) != 1 { + log.Info("invalid number of head headers (!= 1)", "peer", p, "len(headers)", len(headers)) + return nil, errBadPeer + } + return headers[0], nil + + case <-timeout: + log.Info("head header timeout", "peer", p) + return nil, errTimeout + + case <-d.bodyCh: + case <-d.stateCh: + case <-d.receiptCh: + // Out of bounds delivery, ignore + } + } +} + +// Not defined in go's stdlib: +func minInt(a, b int) int { + if a < b { + return a + } + return b +} + +// Fetches headers between `from` and `to`, inclusive. +// Assumes invariant: from <= to. +func (d *Downloader) fetchBoundedHeaders(p *peerConnection, from uint64, to uint64) error { + log.Info("directing header downloads", "peer", p, "from", from, "to", to) + defer log.Info("header download terminated", "peer", p) + + // Create a timeout timer, and the associated header fetcher + skeleton := true // Skeleton assembly phase or finishing up + request := time.Now() // time of the last skeleton fetch request + timeout := time.NewTimer(0) // timer to dump a non-responsive active peer + <-timeout.C // timeout channel should be initially empty + defer timeout.Stop() + + getHeaders := func(from uint64) { + request = time.Now() + timeout.Reset(d.requestTTL()) + + skeletonStart := from + uint64(MaxHeaderFetch) - 1 + + if skeleton { + if skeletonStart > to { + skeleton = false + } + } + + if skeleton { + numSkeletonHeaders := minInt(MaxSkeletonSize, (int(to-from)+1)/MaxHeaderFetch) + log.Trace("fetching skeleton headers", "peer", p, "num skeleton headers", numSkeletonHeaders, "from", from) + go p.peer.RequestHeadersByNumber(skeletonStart, numSkeletonHeaders, MaxHeaderFetch-1, false) + } else { + // There are not enough headers remaining to warrant a skeleton fetch. + // Grab all of the remaining headers. + + numHeaders := int(to-from) + 1 + log.Trace("fetching full headers", "peer", p, "num headers", numHeaders, "from", from) + go p.peer.RequestHeadersByNumber(from, numHeaders, 0, false) + } + } + // Start pulling the header chain skeleton until all is done + getHeaders(from) + + for { + select { + case <-d.cancelCh: + return errCancelHeaderFetch + + case packet := <-d.headerCh: + // Make sure the active peer is giving us the skeleton headers + if packet.PeerId() != p.id { + log.Info("Received headers from incorrect peer", "peer id", packet.PeerId()) + break + } + headerReqTimer.UpdateSince(request) + timeout.Stop() + + headers := packet.(*headerPack).headers + + // If we received a skeleton batch, resolve internals concurrently + if skeleton { + filled, proced, err := d.fillHeaderSkeleton(from, headers) + if err != nil { + log.Debug("skeleton chain invalid", "peer", p, "err", err) + return errInvalidChain + } + headers = filled[proced:] + from += uint64(proced) + } + // Insert all the new headers and fetch the next batch + if len(headers) > 0 { + log.Trace("schedule headers", "peer", p, "num headers", len(headers), "from", from) + select { + case d.headerProcCh <- headers: + case <-d.cancelCh: + return errCancelHeaderFetch + } + from += uint64(len(headers)) + } + + if from <= to { + getHeaders(from) + } else { + // Notify the content fetchers that no more headers are inbound and return. + select { + case d.headerProcCh <- nil: + return nil + case <-d.cancelCh: + return errCancelHeaderFetch + } + } + + case <-timeout.C: + // Header retrieval timed out, consider the peer bad and drop + log.Info("header request timed out", "peer", p) + headerTimeoutMeter.Mark(1) + d.dropPeer(p.id) + + // Finish the sync gracefully instead of dumping the gathered data though + for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh} { + select { + case ch <- false: + case <-d.cancelCh: + } + } + select { + case d.headerProcCh <- nil: + case <-d.cancelCh: + } + return errBadPeer + } + } +} diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index f9092175cb..4dc5727bba 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -41,6 +42,9 @@ func init() { lightMaxForkAncestry = 10000 blockCacheItems = 1024 fsHeaderContCheck = 500 * time.Millisecond + + // set immutability threshold to 10000 as well + params.SetQuorumImmutabilityThreshold(10000) } // downloadTester is a test simulator for mocking out local block chain. diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index d866ceabce..3f5749ac99 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -26,6 +26,8 @@ const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks FastSync // Quickly download the headers, full sync only at the chain head LightSync // Download only the headers and terminate afterwards + // Used by raft: + BoundedFullSync SyncMode = 100 // Perform a full sync until the requested hash, and no further ) func (mode SyncMode) IsValid() bool { diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index 852c250dc2..72bd5fdaef 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -30,6 +30,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" ) @@ -524,7 +525,7 @@ func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerC idle, total := make([]*peerConnection, 0, len(ps.peers)), 0 for _, p := range ps.peers { - if p.version >= minProtocol && p.version <= maxProtocol { + if p.version >= minProtocol && p.version <= maxProtocol || p.version == consensus.Istanbul99 { if idleCheck(p) { idle = append(idle, p) } diff --git a/eth/filters/api.go b/eth/filters/api.go index 30d7b71c31..98ec66a485 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/rpc" ) @@ -346,7 +347,11 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([ if err != nil { return nil, err } - return returnLogs(logs), err + authLogs, err := api.filterUnAuthorized(ctx, logs) + if err != nil { + return nil, err + } + return returnLogs(authLogs), err } // UninstallFilter removes the filter with the given filter id. @@ -401,7 +406,11 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty if err != nil { return nil, err } - return returnLogs(logs), nil + authLogs, err := api.filterUnAuthorized(ctx, logs) + if err != nil { + return nil, err + } + return returnLogs(authLogs), nil } // GetFilterChanges returns the logs for the filter with the given id since @@ -411,7 +420,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty // (pending)Log filters return []Log. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges -func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { +func (api *PublicFilterAPI) GetFilterChanges(ctx context.Context, id rpc.ID) (interface{}, error) { api.filtersMu.Lock() defer api.filtersMu.Unlock() @@ -431,7 +440,11 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { case LogsSubscription, MinedAndPendingLogsSubscription: logs := f.logs f.logs = nil - return returnLogs(logs), nil + authLogs, err := api.filterUnAuthorized(ctx, logs) + if err != nil { + return nil, err + } + return returnLogs(authLogs), nil } } @@ -574,3 +587,32 @@ func decodeTopic(s string) (common.Hash, error) { } return common.BytesToHash(b), err } + +// Quorum +// Perform authorization check for each logs based on the contract addresses +func (api *PublicFilterAPI) filterUnAuthorized(ctx context.Context, logs []*types.Log) ([]*types.Log, error) { + if len(logs) == 0 { + return logs, nil + } + if authToken, ok := api.backend.SupportsMultitenancy(ctx); ok { + filteredLogs := make([]*types.Log, 0) + for _, l := range logs { + extraDataReader, err := api.backend.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(l.BlockNumber)) + if err != nil { + return nil, fmt.Errorf("no account extra data reader at block %v: %w", l.BlockNumber, err) + } + attrBuilder := multitenancy.NewContractSecurityAttributeBuilder().Read().Private() + managedParties, err := extraDataReader.GetManagedParties(l.Address) + if errors.Is(err, common.ErrNotPrivateContract) { + attrBuilder.Public() + } else if err != nil { + return nil, fmt.Errorf("contract %s not found in the index due to %s", l.Address.Hex(), err.Error()) + } + if ok, _ := api.backend.IsAuthorized(ctx, authToken, attrBuilder.Parties(managedParties).Build()); ok { + filteredLogs = append(filteredLogs, l) + } + } + return filteredLogs, nil + } + return logs, nil +} diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 17635837af..0273248a3e 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -24,13 +24,18 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/rpc" ) type Backend interface { + multitenancy.AuthorizationProvider + ChainDb() ethdb.Database HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) @@ -45,6 +50,9 @@ type Backend interface { BloomStatus() (uint64, uint64) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) + + // AccountExtraDataStateGetterByNumber returns state getter at a given block height + AccountExtraDataStateGetterByNumber(ctx context.Context, number rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) } // Filter can be used to retrieve and filter logs. @@ -231,7 +239,11 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e // blockLogs returns the logs matching the filter criteria within a single block. func (f *Filter) blockLogs(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { - if bloomFilter(header.Bloom, f.addresses, f.topics) { + // Quorum + // Apply bloom filter for both public bloom and private bloom + bloomMatches := bloomFilter(header.Bloom, f.addresses, f.topics) || + bloomFilter(rawdb.GetPrivateBlockBloom(f.db, header.Number.Uint64()), f.addresses, f.topics) + if bloomMatches { found, err := f.checkMatches(ctx, header) if err != nil { return logs, err diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index c8d1d43abb..3e9dd58601 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -25,17 +25,20 @@ import ( "testing" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "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" ) type testBackend struct { @@ -152,6 +155,18 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc }() } +func (b *testBackend) SupportsMultitenancy(rpcCtx context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) { + return nil, false +} + +func (b *testBackend) AccountExtraDataStateGetterByNumber(context.Context, rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) { + return nil, nil +} + +func (b *testBackend) IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*multitenancy.ContractSecurityAttribute) (bool, error) { + return true, nil +} + // TestBlockSubscription tests if a block subscription returns block hashes for posted chain events. // It creates multiple subscriptions: // - one at the start and should receive all posted chain events and a second (blockHashes) @@ -235,7 +250,7 @@ func TestPendingTxFilter(t *testing.T) { timeout := time.Now().Add(1 * time.Second) for { - results, err := api.GetFilterChanges(fid0) + results, err := api.GetFilterChanges(context.Background(), fid0) if err != nil { t.Fatalf("Unable to retrieve logs: %v", err) } @@ -435,7 +450,7 @@ func TestLogFilter(t *testing.T) { var fetched []*types.Log timeout := time.Now().Add(1 * time.Second) for { // fetch all expected logs - results, err := api.GetFilterChanges(tt.id) + results, err := api.GetFilterChanges(context.Background(), tt.id) if err != nil { t.Fatalf("Unable to fetch logs: %v", err) } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index f45720d5a9..a3e47fa59d 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -112,6 +112,7 @@ func TestFilters(t *testing.T) { hash2 = common.BytesToHash([]byte("topic2")) hash3 = common.BytesToHash([]byte("topic3")) hash4 = common.BytesToHash([]byte("topic4")) + hash5 = common.BytesToHash([]byte("privateTopic")) ) defer db.Close() @@ -149,6 +150,19 @@ func TestFilters(t *testing.T) { } gen.AddUncheckedReceipt(receipt) gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, big.NewInt(998), nil)) + // Add pseudo Quorum private transaction + privateReceipt := types.NewReceipt(nil, false, 0) + privateReceipt.Logs = []*types.Log{ + { + Address: addr, + Topics: []common.Hash{hash5}, + }, + } + if err := rawdb.WritePrivateBlockBloom(db, 999, []*types.Receipt{privateReceipt}); err != nil { + t.Fatal(err) + } + gen.AddUncheckedReceipt(privateReceipt) + gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, big.NewInt(998), nil)) case 999: receipt := types.NewReceipt(nil, false, 0) receipt.Logs = []*types.Log{ @@ -222,4 +236,26 @@ func TestFilters(t *testing.T) { if len(logs) != 0 { t.Error("expected 0 log, got", len(logs)) } + + // Quorum + + // Test individual private log with NewBlockFilter (query filter with block hash) + filter = NewBlockFilter(backend, chain[998].Hash(), nil, [][]common.Hash{{hash5}}) + + logs, _ = filter.Logs(context.Background()) + if len(logs) != 1 { + t.Error("expected 1 log, got", len(logs)) + } + if len(logs) > 0 && logs[0].Topics[0] != hash5 { + t.Errorf("expected log[0].Topics[0] to be %x, got %x", hash5, logs[0].Topics[0]) + } + + // Test a mix of public and private logs with NewBlockFilter (query filter with block hash) + filter = NewBlockFilter(backend, chain[998].Hash(), nil, [][]common.Hash{{hash3, hash5}}) + + logs, _ = filter.Logs(context.Background()) + if len(logs) != 2 { + t.Error("expected 2 log, got", len(logs)) + } + } diff --git a/eth/gen_config.go b/eth/gen_config.go index 2defc36cb2..c719ea8546 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/istanbul" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -46,6 +47,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TxPool core.TxPoolConfig GPO gasprice.Config EnablePreimageRecording bool + Istanbul istanbul.Config DocRoot string `toml:"-"` EWASMInterpreter string EVMInterpreter string @@ -84,6 +86,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TxPool = c.TxPool enc.GPO = c.GPO enc.EnablePreimageRecording = c.EnablePreimageRecording + enc.Istanbul = c.Istanbul enc.DocRoot = c.DocRoot enc.EWASMInterpreter = c.EWASMInterpreter enc.EVMInterpreter = c.EVMInterpreter @@ -126,6 +129,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TxPool *core.TxPoolConfig GPO *gasprice.Config EnablePreimageRecording *bool + Istanbul *istanbul.Config DocRoot *string `toml:"-"` EWASMInterpreter *string EVMInterpreter *string @@ -225,6 +229,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.EnablePreimageRecording != nil { c.EnablePreimageRecording = *dec.EnablePreimageRecording } + if dec.Istanbul != nil { + c.Istanbul = *dec.Istanbul + } if dec.DocRoot != nil { c.DocRoot = *dec.DocRoot } diff --git a/eth/handler.go b/eth/handler.go index 9180706ada..6d0ec1ffed 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -28,9 +28,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" "github.com/ethereum/go-ethereum/ethdb" @@ -95,13 +98,17 @@ type ProtocolManager struct { wg sync.WaitGroup peerWG sync.WaitGroup + // Quorum + raftMode bool + engine consensus.Engine + // Test fields or hooks broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation } // NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // with the Ethereum network. -func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) { +func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash, raftMode bool) (*ProtocolManager, error) { // Create the protocol manager with the base fields manager := &ProtocolManager{ networkID: networkID, @@ -114,7 +121,15 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh whitelist: whitelist, txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), + raftMode: raftMode, + engine: engine, + } + + // Quorum + if handler, ok := manager.engine.(consensus.Handler); ok { + handler.SetBroadcaster(manager) } + // /Quorum if mode == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the fast @@ -205,6 +220,7 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh } func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol { + // Quorum: Set p2p.Protocol info from engine.Protocol() length, ok := protocolLengths[version] if !ok { panic("makeProtocol for unknown version") @@ -215,7 +231,7 @@ func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol { Version: version, Length: length, Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return pm.runPeer(pm.newPeer(int(version), p, rw, pm.txpool.Get)) + return pm.runPeer(pm.newPeer(int(version), p, rw, pm.txpool.Get), protocolName) }, NodeInfo: func() interface{} { return pm.NodeInfo() @@ -259,10 +275,19 @@ func (pm *ProtocolManager) Start(maxPeers int) { pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh) go pm.txBroadcastLoop() - // broadcast mined blocks - pm.wg.Add(1) - pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{}) - go pm.minedBroadcastLoop() + // Quorum + if !pm.raftMode { + // broadcast mined blocks + pm.wg.Add(1) + pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{}) + go pm.minedBroadcastLoop() + } else { + // We set this immediately in raft mode to make sure the miner never drops + // incoming txes. Raft mode doesn't use the fetcher or downloader, and so + // this would never be set otherwise. + atomic.StoreUint32(&pm.acceptTxs, 1) + } + // /Quorum // start sync handlers pm.wg.Add(2) @@ -271,8 +296,10 @@ func (pm *ProtocolManager) Start(maxPeers int) { } func (pm *ProtocolManager) Stop() { - pm.txsSub.Unsubscribe() // quits txBroadcastLoop - pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop + pm.txsSub.Unsubscribe() // quits txBroadcastLoop + if !pm.raftMode { + pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop + } // Quit chainSync and txsync64. // After this is done, no new peers will be accepted. @@ -293,18 +320,20 @@ func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, ge return newPeer(pv, p, rw, getPooledTx) } -func (pm *ProtocolManager) runPeer(p *peer) error { +// Quorum - added protoName argument +func (pm *ProtocolManager) runPeer(p *peer, protoName string) error { if !pm.chainSync.handlePeerEvent(p) { return p2p.DiscQuitting } pm.peerWG.Add(1) defer pm.peerWG.Done() - return pm.handle(p) + return pm.handle(p, protoName) } +// quorum: protoname is either "eth" or a subprotocol that overrides "eth", e.g. legacy "istanbul/99" // handle is the callback invoked to manage the life cycle of an eth peer. When // this function terminates, the peer is disconnected. -func (pm *ProtocolManager) handle(p *peer) error { +func (pm *ProtocolManager) handle(p *peer, protoName string) error { // Ignore maxPeers if this is a trusted peer if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted { return p2p.DiscTooManyPeers @@ -319,13 +348,13 @@ func (pm *ProtocolManager) handle(p *peer) error { number = head.Number.Uint64() td = pm.blockchain.GetTd(hash, number) ) - if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkid.NewID(pm.blockchain), pm.forkFilter); err != nil { - p.Log().Debug("Ethereum handshake failed", "err", err) + if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkid.NewID(pm.blockchain), pm.forkFilter, protoName); err != nil { + p.Log().Debug("Ethereum handshake failed", "protoName", protoName, "err", err) return err } // Register the peer locally - if err := pm.peers.Register(p, pm.removePeer); err != nil { + if err := pm.peers.Register(p, pm.removePeer, protoName); err != nil { p.Log().Error("Ethereum peer registration failed", "err", err) return err } @@ -366,6 +395,11 @@ func (pm *ProtocolManager) handle(p *peer) error { return err } } + + // Quorum notify other subprotocols that the eth peer is ready, and has been added to the peerset. + p.EthPeerRegistered <- struct{}{} + // Quorum + // Handle incoming messages until the connection is torn down for { if err := pm.handleMsg(p); err != nil { @@ -388,6 +422,28 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } defer msg.Discard() + // Quorum + if pm.raftMode { + switch msg.Code { + case TransactionMsg, PooledTransactionsMsg, + GetPooledTransactionsMsg, NewPooledTransactionHashesMsg, + GetBlockHeadersMsg, BlockHeadersMsg, + GetBlockBodiesMsg, BlockBodiesMsg: + // supported by Raft + default: + log.Info("raft: ignoring message", "code", msg.Code) + return nil + } + } else if handler, ok := pm.engine.(consensus.Handler); ok { // quorum: NewBlock required for consensus, e.g. "istanbul" + pubKey := p.Node().Pubkey() + addr := crypto.PubkeyToAddress(*pubKey) + handled, err := handler.HandleMsg(addr, msg) + if handled { + return err + } + } + // End Quorum + // Handle the message depending on its contents switch { case msg.Code == StatusMsg: @@ -806,6 +862,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { return nil } +// Quorum +func (pm *ProtocolManager) Enqueue(id string, block *types.Block) { + pm.blockFetcher.Enqueue(id, block) +} + // BroadcastBlock will either propagate a block to a subset of its peers, or // will only announce its availability (depending what's requested). func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { @@ -847,6 +908,12 @@ func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propaga annos = make(map[*peer][]common.Hash) ) // Broadcast transactions to a batch of peers not knowing about it + // NOTE: Raft-based consensus currently assumes that geth broadcasts + // transactions to all peers in the network. A previous comment here + // indicated that this logic might change in the future to only send to a + // subset of peers. If this change occurs upstream, a merge conflict should + // arise here, and we should add logic to send to *all* peers in raft mode. + if propagate { for _, tx := range txs { peers := pm.peers.PeersWithoutTx(tx.Hash()) @@ -920,16 +987,60 @@ type NodeInfo struct { Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block + Consensus string `json:"consensus"` // Consensus mechanism in use } // NodeInfo retrieves some protocol metadata about the running host node. func (pm *ProtocolManager) NodeInfo() *NodeInfo { currentBlock := pm.blockchain.CurrentBlock() + // //Quorum + // + // changes done to fetch maxCodeSize dynamically based on the + // maxCodeSizeConfig changes + // /Quorum + chainConfig := pm.blockchain.Config() + chainConfig.MaxCodeSize = uint64(chainConfig.GetMaxCodeSize(pm.blockchain.CurrentBlock().Number()) / 1024) + return &NodeInfo{ Network: pm.networkID, Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), Genesis: pm.blockchain.Genesis().Hash(), - Config: pm.blockchain.Config(), + Config: chainConfig, Head: currentBlock.Hash(), + Consensus: pm.getConsensusAlgorithm(), + } +} + +// Quorum +func (pm *ProtocolManager) getConsensusAlgorithm() string { + var consensusAlgo string + if pm.raftMode { // raft does not use consensus interface + consensusAlgo = "raft" + } else { + switch pm.engine.(type) { + case consensus.Istanbul: + consensusAlgo = "istanbul" + case *clique.Clique: + consensusAlgo = "clique" + case *ethash.Ethash: + consensusAlgo = "ethash" + default: + consensusAlgo = "unknown" + } } + return consensusAlgo } + +func (self *ProtocolManager) FindPeers(targets map[common.Address]bool) map[common.Address]consensus.Peer { + m := make(map[common.Address]consensus.Peer) + for _, p := range self.peers.Peers() { + pubKey := p.Node().Pubkey() + addr := crypto.PubkeyToAddress(*pubKey) + if targets[addr] { + m[addr] = p + } + } + return m +} + +// End Quorum diff --git a/eth/handler_test.go b/eth/handler_test.go index fc6c6f2745..aaae6036b0 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -38,6 +39,45 @@ import ( "github.com/ethereum/go-ethereum/params" ) +// Tests that correct consensus mechanism details are returned in NodeInfo. +func TestNodeInfo(t *testing.T) { + + // Define the tests to be run + tests := []struct { + consensus string + cliqueConfig *params.CliqueConfig + istanbulConfig *params.IstanbulConfig + raftMode bool + }{ + {"ethash", nil, nil, false}, + {"raft", nil, nil, true}, + {"istanbul", nil, ¶ms.IstanbulConfig{Epoch: 1, ProposerPolicy: 1, Ceil2Nby3Block: big.NewInt(0)}, false}, + {"clique", ¶ms.CliqueConfig{Period: 1, Epoch: 1}, nil, false}, + } + + // Make sure anything we screw up is restored + backup := consensus.EthProtocol.Versions + defer func() { consensus.EthProtocol.Versions = backup }() + + // Try all available consensus mechanisms and check for errors + for i, tt := range tests { + + pm, _, err := newTestProtocolManagerConsensus(tt.consensus, tt.cliqueConfig, tt.istanbulConfig, tt.raftMode) + + if pm != nil { + defer pm.Stop() + } + if err == nil { + pmConsensus := pm.getConsensusAlgorithm() + if tt.consensus != pmConsensus { + t.Errorf("test %d: consensus type error, wanted %v but got %v", i, tt.consensus, pmConsensus) + } + } else { + t.Errorf("test %d: consensus type error %v", i, err) + } + } +} + // Tests that block headers can be retrieved from a remote chain based on user queries. func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) } func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } @@ -352,7 +392,7 @@ func testGetNodeData(t *testing.T, protocol int) { trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil) for j, acc := range accounts { - state, _ := pm.blockchain.State() + state, _, _ := pm.blockchain.State() bw := state.GetBalance(acc) bh := trie.GetBalance(acc) @@ -495,7 +535,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo if err != nil { t.Fatalf("failed to create new blockchain: %v", err) } - pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil) + pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil, false) if err != nil { t.Fatalf("failed to start test protocol manager: %v", err) } @@ -582,7 +622,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) { if err != nil { t.Fatalf("failed to create new blockchain: %v", err) } - pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil) + pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil, false) if err != nil { t.Fatalf("failed to start test protocol manager: %v", err) } @@ -646,7 +686,7 @@ func TestBroadcastMalformedBlock(t *testing.T) { if err != nil { t.Fatalf("failed to create new blockchain: %v", err) } - pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil) + pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil, false) if err != nil { t.Fatalf("failed to start test protocol manager: %v", err) } diff --git a/eth/helper_test.go b/eth/helper_test.go index 65effcc165..5c0c206134 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -28,6 +28,11 @@ import ( "sync" "testing" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/istanbul" + istanbulBackend "github.com/ethereum/go-ethereum/consensus/istanbul/backend" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" @@ -68,7 +73,59 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func if _, err := blockchain.InsertChain(chain); err != nil { panic(err) } - pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil) + pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil, false) + if err != nil { + return nil, nil, err + } + pm.Start(1000) + return pm, db, nil +} + +// newTestProtocolManagerConsensus creates a new protocol manager for testing purposes, +// that uses the specified consensus mechanism. +func newTestProtocolManagerConsensus(consensusAlgo string, cliqueConfig *params.CliqueConfig, istanbulConfig *params.IstanbulConfig, raftMode bool) (*ProtocolManager, ethdb.Database, error) { + + config := params.QuorumTestChainConfig + config.Clique = cliqueConfig + config.Istanbul = istanbulConfig + + var ( + blocks = 0 + evmux = new(event.TypeMux) + engine consensus.Engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + gspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}}, + } + genesis = gspec.MustCommit(db) + blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) + ) + chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, nil) + if _, err := blockchain.InsertChain(chain); err != nil { + panic(err) + } + + switch consensusAlgo { + case "raft": + engine = ethash.NewFaker() //raft doesn't use engine, but just mirroring what runtime code does + + case "istanbul": + var istanbul istanbul.Config + config.Istanbul.Epoch = istanbulConfig.Epoch + config.Istanbul.ProposerPolicy = istanbulConfig.ProposerPolicy + + nodeKey, _ := crypto.GenerateKey() + engine = istanbulBackend.New(&istanbul, nodeKey, db) + + case "clique": + engine = clique.New(config.Clique, db) + + default: + engine = ethash.NewFaker() + } + + pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{added: nil}, engine, blockchain, db, 1, nil, raftMode) if err != nil { return nil, nil, err } @@ -175,7 +232,7 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te rand.Read(id[:]) peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get) errc := make(chan error, 1) - go func() { errc <- pm.runPeer(peer) }() + go func() { errc <- pm.runPeer(peer, protocolName) }() tp := &testPeer{app: app, net: net, peer: peer} // Execute any implicitly requested handshakes and return diff --git a/eth/peer.go b/eth/peer.go index 468cfb9185..fc8851d1f6 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -25,6 +25,7 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p" @@ -107,6 +108,8 @@ type peer struct { getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool term chan struct{} // Termination channel to stop the broadcaster + + consensusRw p2p.MsgReadWriter // Quorum: this is the RW for the consensus devp2p protocol, e.g. "istanbul/100" } func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { @@ -333,6 +336,16 @@ func (p *peer) MarkTransaction(hash common.Hash) { p.knownTxs.Add(hash) } +// Quorum +// Quorum: this was added with the origin "istanbul" implementation. +// Send writes an RLP-encoded message with the given code. +// data should encode as an RLP list. +func (p *peer) Send(msgcode uint64, data interface{}) error { + return p2p.Send(p.rw, msgcode, data) +} + +// End Quorum + // SendTransactions64 sends transactions to the peer and includes the hashes // in its transaction hash set for future reference. // @@ -564,17 +577,19 @@ func (p *peer) RequestTxs(hashes []common.Hash) error { // Handshake executes the eth protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { +func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, protocolName string) error { // Send out own handshake in a new thread errc := make(chan error, 2) var ( - status63 statusData63 // safe to read after two values have been received from errc - status statusData // safe to read after two values have been received from errc + status63 statusData63 // safe to read after two values have been received from errc + status statusData // safe to read after two values have been received from errc + istanbulOld = protocolName == "istanbul" && p.version == consensus.Istanbul64 + istanbulNew = protocolName == "istanbul" && p.version == consensus.Istanbul99 ) go func() { switch { - case p.version == eth63: + case p.version == eth63 || istanbulOld: errc <- p2p.Send(p.rw, StatusMsg, &statusData63{ ProtocolVersion: uint32(p.version), NetworkId: network, @@ -582,7 +597,7 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis CurrentBlock: head, GenesisBlock: genesis, }) - case p.version >= eth64: + case p.version >= eth64 || istanbulNew: errc <- p2p.Send(p.rw, StatusMsg, &statusData{ ProtocolVersion: uint32(p.version), NetworkID: network, @@ -597,9 +612,9 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis }() go func() { switch { - case p.version == eth63: + case p.version == eth63 || istanbulOld: errc <- p.readStatusLegacy(network, &status63, genesis) - case p.version >= eth64: + case p.version >= eth64 || istanbulNew: errc <- p.readStatus(network, &status, genesis, forkFilter) default: panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) @@ -618,9 +633,9 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis } } switch { - case p.version == eth63: + case p.version == eth63 || istanbulOld: p.td, p.head = status63.TD, status63.CurrentBlock - case p.version >= eth64: + case p.version >= eth64 || istanbulNew: p.td, p.head = status.TD, status.Head default: panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) @@ -707,10 +722,14 @@ func newPeerSet() *peerSet { } } +// Quorum protoName is needed to check if the peer is running eth protocol or a legacy quorum +// consensus protocol, e.g. istanbul/99 which would not support p.announceTransactions() / NewPooledTransactionHashesMsg +// Quorum + // Register injects a new peer into the working set, or returns an error if the // peer is already known. If a new peer it registered, its broadcast loop is also // started. -func (ps *peerSet) Register(p *peer, removePeer func(string)) error { +func (ps *peerSet) Register(p *peer, removePeer func(string), protoName string) error { ps.lock.Lock() defer ps.lock.Unlock() @@ -724,7 +743,9 @@ func (ps *peerSet) Register(p *peer, removePeer func(string)) error { go p.broadcastBlocks(removePeer) go p.broadcastTransactions(removePeer) - if p.version >= eth65 { + // Quorum passes in and checks the protoName to see if it is "eth" + // as it could also be a legacy protocol, e.g. "istanbul/99", protocolName is always set to "eth" for the eth service. + if p.version >= eth65 && protoName == protocolName { go p.announceTransactions(removePeer) } return nil @@ -746,6 +767,21 @@ func (ps *peerSet) Unregister(id string) error { return nil } +// Quorum +// Peers returns all registered peers +func (ps *peerSet) Peers() map[string]*peer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + set := make(map[string]*peer) + for id, p := range ps.peers { + set[id] = p + } + return set +} + +// End Quorum + // Peer retrieves the registered peer with the given id. func (ps *peerSet) Peer(id string) *peer { ps.lock.RLock() diff --git a/eth/protocol.go b/eth/protocol.go index dc75d6b31a..cd536c9755 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -37,7 +37,7 @@ const ( ) // protocolName is the official short name of the protocol used during capability negotiation. -const protocolName = "eth" +var protocolName = "eth" // ProtocolVersions are the supported versions of the eth protocol (first is primary). var ProtocolVersions = []uint{eth65, eth64, eth63} diff --git a/eth/protocol_test.go b/eth/protocol_test.go index fc916a2263..31d8a28763 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -181,8 +181,8 @@ func TestForkIDSplit(t *testing.T) { blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil) blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil) - ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil) - ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil) + ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil, false) + ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil, false) ) ethNoFork.Start(1000) ethProFork.Start(1000) @@ -193,8 +193,8 @@ func TestForkIDSplit(t *testing.T) { peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) errc := make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() + go func() { errc <- ethNoFork.handle(peerProFork, protocolName) }() + go func() { errc <- ethProFork.handle(peerNoFork, protocolName) }() select { case err := <-errc: @@ -212,8 +212,8 @@ func TestForkIDSplit(t *testing.T) { peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) errc = make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() + go func() { errc <- ethNoFork.handle(peerProFork, protocolName) }() + go func() { errc <- ethProFork.handle(peerNoFork, protocolName) }() select { case err := <-errc: @@ -231,8 +231,8 @@ func TestForkIDSplit(t *testing.T) { peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) errc = make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() + go func() { errc <- ethNoFork.handle(peerProFork, protocolName) }() + go func() { errc <- ethProFork.handle(peerNoFork, protocolName) }() select { case err := <-errc: @@ -381,8 +381,8 @@ func testSyncTransaction(t *testing.T, propagtion bool) { // Sync up the two peers io1, io2 := p2p.MsgPipe() - go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get)) - go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get)) + go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get), protocolName) + go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get), protocolName) time.Sleep(250 * time.Millisecond) pmFetcher.doSync(peerToSyncOp(downloader.FullSync, pmFetcher.peers.BestPeer())) diff --git a/eth/quorum_protocol.go b/eth/quorum_protocol.go new file mode 100644 index 0000000000..c5202b7769 --- /dev/null +++ b/eth/quorum_protocol.go @@ -0,0 +1,209 @@ +package eth + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// Quorum: quorum_protocol enables the eth service to return two different protocols, one for the eth mainnet "eth" service, +// and one for the quorum specific consensus algo, obtained from engine.consensus +// 2021 Jan in the future consensus (istanbul) may run from its own service and use a single subprotocol there, +// instead of overloading the eth service. + +var ( + // errEthPeerNil is returned when no eth peer is found to be associated with a p2p peer. + errEthPeerNil = errors.New("eth peer was nil") + errEthPeerNotRegistered = errors.New("eth peer was not registered") +) + +// quorum consensus Protocol variables are optionally set in addition to the "eth" protocol variables (eth/protocol.go). +var quorumConsensusProtocolName = "" + +// ProtocolVersions are the supported versions of the quorum consensus protocol (first is primary), e.g. []uint{Istanbul64, Istanbul99, Istanbul100}. +var quorumConsensusProtocolVersions []uint + +// protocol Length describe the number of messages support by the protocol/version map[uint]uint64{Istanbul64: 18, Istanbul99: 18, Istanbul100: 18} +var quorumConsensusProtocolLengths map[uint]uint64 + +// makeQuorumConsensusProtocol is similar to eth/handler.go -> makeProtocol. Called from eth/handler.go -> Protocols. +// returns the supported subprotocol to the p2p server. +// The Run method starts the protocol and is called by the p2p server. The quorum consensus subprotocol, +// leverages the peer created and managed by the "eth" subprotocol. +// The quorum consensus protocol requires that the "eth" protocol is running as well. +func (pm *ProtocolManager) makeQuorumConsensusProtocol(ProtoName string, version uint, length uint64) p2p.Protocol { + + return p2p.Protocol{ + Name: ProtoName, + Version: version, + Length: length, + // no new peer created, uses the "eth" peer, so no peer management needed. + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + /* + * 1. wait for the eth protocol to create and register an eth peer. + * 2. get the associate eth peer that was registered by he "eth" protocol. + * 2. add the rw protocol for the quorum subprotocol to the eth peer. + * 3. start listening for incoming messages. + * 4. the incoming message will be sent on the quorum specific subprotocol, e.g. "istanbul/100". + * 5. send messages to the consensus engine handler. + * 7. messages to other to other peers listening to the subprotocol can be sent using the + * (eth)peer.ConsensusSend() which will write to the protoRW. + */ + // wait for the "eth" protocol to create and register the peer (added to peerset) + select { + case <-p.EthPeerRegistered: + // the ethpeer should be registered, try to retrieve it and start the consensus handler. + p2pPeerId := fmt.Sprintf("%x", p.ID().Bytes()[:8]) + ethPeer := pm.peers.Peer(p2pPeerId) + if ethPeer != nil { + p.Log().Debug("consensus subprotocol retrieved eth peer from peerset", "ethPeer.id", ethPeer.id, "ProtoName", ProtoName) + // add the rw protocol for the quorum subprotocol to the eth peer. + ethPeer.addConsensusProtoRW(rw) + return pm.handleConsensusLoop(p, rw) + } + p.Log().Error("consensus subprotocol retrieved nil eth peer from peerset", "ethPeer.id", ethPeer) + return errEthPeerNil + case <-p.EthPeerDisconnected: + return errEthPeerNotRegistered + } + }, + NodeInfo: func() interface{} { + return pm.NodeInfo() + }, + PeerInfo: func(id enode.ID) interface{} { + if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { + return p.Info() + } + return nil + }, + } +} + +func (pm *ProtocolManager) handleConsensusLoop(p *p2p.Peer, protoRW p2p.MsgReadWriter) error { + // Handle incoming messages until the connection is torn down + for { + if err := pm.handleConsensus(p, protoRW); err != nil { + p.Log().Debug("Ethereum quorum message handling failed", "err", err) + return err + } + } +} + +// This is a no-op because the eth handleMsg main loop handle ibf message as well. +func (pm *ProtocolManager) handleConsensus(p *p2p.Peer, protoRW p2p.MsgReadWriter) error { + // Read the next message from the remote peer (in protoRW), and ensure it's fully consumed + msg, err := protoRW.ReadMsg() + if err != nil { + return err + } + if msg.Size > protocolMaxMsgSize { + return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) + } + defer msg.Discard() + + // See if the consensus engine protocol can handle this message, e.g. istanbul will check for message is + // istanbulMsg = 0x11, and NewBlockMsg = 0x07. + handled, err := pm.handleConsensusMsg(p, msg) + if handled { + p.Log().Debug("consensus message was handled by consensus engine", "handled", handled, + "quorumConsensusProtocolName", quorumConsensusProtocolName, "err", err) + return err + } + + return nil +} + +func (pm *ProtocolManager) handleConsensusMsg(p *p2p.Peer, msg p2p.Msg) (bool, error) { + if handler, ok := pm.engine.(consensus.Handler); ok { + pubKey := p.Node().Pubkey() + addr := crypto.PubkeyToAddress(*pubKey) + handled, err := handler.HandleMsg(addr, msg) + return handled, err + } + return false, nil +} + +// makeLegacyProtocol is basically a copy of the eth makeProtocol, but for legacy subprotocols, e.g. "istanbul/99" "istabnul/64" +// If support legacy subprotocols is removed, remove this and associated code as well. +// If quorum is using a legacy protocol then the "eth" subprotocol should not be available. +func (pm *ProtocolManager) makeLegacyProtocol(protoName string, version uint, length uint64) p2p.Protocol { + log.Debug("registering a legacy protocol ", "protoName", protoName) + return p2p.Protocol{ + Name: protoName, + Version: version, + Length: length, + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + peer := pm.newPeer(int(version), p, rw, pm.txpool.Get) + peer.addConsensusProtoRW(rw) + return pm.runPeer(peer, protoName) + }, + NodeInfo: func() interface{} { + return pm.NodeInfo() + }, + PeerInfo: func(id enode.ID) interface{} { + if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { + return p.Info() + } + return nil + }, + } +} + +func (s *Ethereum) quorumConsensusProtocols() []p2p.Protocol { + protos := make([]p2p.Protocol, len(quorumConsensusProtocolVersions)) + for i, vsn := range quorumConsensusProtocolVersions { + // if we have a legacy protocol, e.g. istanbul/99, istanbul/64 then the protocol handler is will be the "eth" + // protocol handler, and the subprotocol "eth" will not be used, but rather the legacy subprotocol will handle + // both eth messages and consensus messages. + if isLegacyProtocol(quorumConsensusProtocolName, vsn) { + length, ok := quorumConsensusProtocolLengths[vsn] + if !ok { + panic("makeProtocol for unknown version") + } + lp := s.protocolManager.makeLegacyProtocol(quorumConsensusProtocolName, vsn, length) + protos[i] = lp + } else { + length, ok := quorumConsensusProtocolLengths[vsn] + if !ok { + panic("makeQuorumConsensusProtocol for unknown version") + } + protos[i] = s.protocolManager.makeQuorumConsensusProtocol(quorumConsensusProtocolName, vsn, length) + } + } + return protos +} + +// istanbul/64, istanbul/99, clique/63, clique/64 all override the "eth" subprotocol. +func isLegacyProtocol(name string, version uint) bool { + // protocols that override "eth" subprotocol and run only the quorum subprotocol. + quorumLegacyProtocols := map[string][]uint{"istanbul": {64, 99}, "clique": {63, 64}} + for lpName, lpVersions := range quorumLegacyProtocols { + if lpName == name { + for _, v := range lpVersions { + if v == version { + return true + } + } + } + } + return false +} + +// Used to send consensus subprotocol messages from an "eth" peer, e.g. "istanbul/100" subprotocol messages. +func (p *peer) SendConsensus(msgcode uint64, data interface{}) error { + if p.consensusRw == nil { + return nil + } + return p2p.Send(p.consensusRw, msgcode, data) +} + +func (p *peer) addConsensusProtoRW(rw p2p.MsgReadWriter) *peer { + p.consensusRw = rw + return p +} diff --git a/eth/sync.go b/eth/sync.go index 0982a9702d..e77eff1d7f 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/permission/core" ) const ( @@ -207,7 +208,9 @@ func (cs *chainSyncer) loop() { for { if op := cs.nextSyncOp(); op != nil { - cs.startSync(op) + if !cs.pm.raftMode { + cs.startSync(op) + } } select { @@ -260,8 +263,19 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { mode, ourTD := cs.modeAndLocalHead() op := peerToSyncOp(mode, peer) if op.td.Cmp(ourTD) <= 0 { + // Quorum + // added for permissions changes to indicate node sync up has started + // if peer's TD is smaller than ours, no sync will happen + core.SetSyncStatus() return nil // We're in sync. } + if mode == downloader.FastSync { + // Make sure the peer's total difficulty we are synchronizing is higher. + if cs.pm.blockchain.GetTdByHash(cs.pm.blockchain.CurrentFastBlock().Hash()).Cmp(ourTD) >= 0 { + // Quorum never use FastSync, no need to execute SetSyncStatus + return nil + } + } return op } diff --git a/eth/sync_test.go b/eth/sync_test.go index ac1e5fad1b..70d6490479 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -48,8 +48,8 @@ func testFastSyncDisabling(t *testing.T, protocol int) { // Sync up the two peers io1, io2 := p2p.MsgPipe() - go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get)) - go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get)) + go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get), protocolName) + go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get), protocolName) time.Sleep(250 * time.Millisecond) op := peerToSyncOp(downloader.FastSync, pmEmpty.peers.BestPeer()) diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index b4de998651..8af6139625 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -51,7 +51,8 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } func runTrace(tracer *Tracer) (json.RawMessage, error) { - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + db := &dummyStatedb{} + env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, db, db, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} @@ -165,8 +166,8 @@ func TestHaltBetweenSteps(t *testing.T) { if err != nil { t.Fatal(err) } - - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + db := &dummyStatedb{} + env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, db, db, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index cd625be0fb..b6664280b4 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -175,7 +175,7 @@ func TestPrestateTracerCreate2(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, statedb, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { @@ -249,7 +249,7 @@ func TestCallTracer(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, statedb, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index a60d732231..ea9403abe9 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -25,6 +25,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -34,7 +35,8 @@ import ( // Client defines typed wrappers for the Ethereum RPC API. type Client struct { - c *rpc.Client + c *rpc.Client + pc privateTransactionManagerClient // Tessera/Constellation client } // Dial connects a client to the given URL. @@ -52,9 +54,28 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { // NewClient creates a client that uses the given RPC client. func NewClient(c *rpc.Client) *Client { - return &Client{c} + return &Client{c, nil} } +// Quorum +// +// NewClientWithPTM creates a client that uses the given RPC client and the privateTransactionManager client +func NewClientWithPTM(c *rpc.Client, ptm privateTransactionManagerClient) *Client { + return &Client{c, ptm} +} + +// provides support for private transactions +func (ec *Client) WithPrivateTransactionManager(rawurl string) (*Client, error) { + var err error + ec.pc, err = newPrivateTransactionManagerClient(rawurl) + if err != nil { + return nil, err + } + return ec, nil +} + +// /Quorum + func (ec *Client) Close() { ec.c.Close() } @@ -509,12 +530,27 @@ func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64 // // If the transaction was a contract creation use the TransactionReceipt method to get the // contract address after the transaction has been mined. -func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { +func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction, args bind.PrivateTxArgs) error { data, err := rlp.EncodeToBytes(tx) if err != nil { return err } - return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) + if args.PrivateFor != nil { + return ec.c.CallContext(ctx, nil, "eth_sendRawPrivateTransaction", common.ToHex(data), bind.PrivateTxArgs{PrivateFor: args.PrivateFor}) + } else { + return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) + } +} + +// Quorum +// +// Retrieve encrypted payload hash from the private transaction manager if configured +func (ec *Client) PreparePrivateTransaction(data []byte, privateFrom string) (common.EncryptedPayloadHash, error) { + if ec.pc == nil { + return common.EncryptedPayloadHash{}, errors.New("missing private transaction manager client configuration") + } + payLoadHash, err := ec.pc.StoreRaw(data, privateFrom) + return payLoadHash, err } func toCallArg(msg ethereum.CallMsg) interface{} { diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 3576d4870e..a2ee42d908 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/assert" ) // Verify that Client implements the ethereum interfaces. @@ -335,3 +336,31 @@ func TestChainID(t *testing.T) { t.Fatalf("ChainID returned wrong number: %+v", id) } } + +func TestClient_PreparePrivateTransaction_whenTypical(t *testing.T) { + testObject := NewClient(nil) + + _, err := testObject.PreparePrivateTransaction([]byte("arbitrary payload"), "arbitrary private from") + + assert.Error(t, err) +} + +func TestClient_PreparePrivateTransaction_whenClientIsConfigured(t *testing.T) { + expectedData := []byte("arbitrary payload") + expectedDataEPH := common.BytesToEncryptedPayloadHash(expectedData) + testObject := NewClient(nil) + testObject.pc = &privateTransactionManagerStubClient{expectedData} + + actualData, err := testObject.PreparePrivateTransaction([]byte("arbitrary payload"), "arbitrary private from") + + assert.NoError(t, err) + assert.Equal(t, expectedDataEPH, actualData) +} + +type privateTransactionManagerStubClient struct { + expectedData []byte +} + +func (s *privateTransactionManagerStubClient) StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) { + return common.BytesToEncryptedPayloadHash(data), nil +} diff --git a/ethclient/privateTransactionManagerClient.go b/ethclient/privateTransactionManagerClient.go new file mode 100644 index 0000000000..07530abbc9 --- /dev/null +++ b/ethclient/privateTransactionManagerClient.go @@ -0,0 +1,73 @@ +package ethclient + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/ethereum/go-ethereum/common" +) + +type privateTransactionManagerClient interface { + StoreRaw(data []byte, privateFrom string) (common.EncryptedPayloadHash, error) +} + +type privateTransactionManagerDefaultClient struct { + rawurl string + httpClient *http.Client +} + +// Create a new client to interact with private transaction manager via a HTTP endpoint +func newPrivateTransactionManagerClient(endpoint string) (privateTransactionManagerClient, error) { + _, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + return &privateTransactionManagerDefaultClient{ + rawurl: endpoint, + httpClient: &http.Client{}, + }, nil +} + +type storeRawReq struct { + Payload string `json:"payload"` + From string `json:"from,omitempty"` +} + +type storeRawResp struct { + Key string `json:"key"` +} + +func (pc *privateTransactionManagerDefaultClient) StoreRaw(data []byte, privateFrom string) (common.EncryptedPayloadHash, error) { + storeRawReq := &storeRawReq{ + Payload: base64.StdEncoding.EncodeToString(data), + From: privateFrom, + } + reqBodyBuf := new(bytes.Buffer) + if err := json.NewEncoder(reqBodyBuf).Encode(storeRawReq); err != nil { + return common.EncryptedPayloadHash{}, err + } + resp, err := pc.httpClient.Post(pc.rawurl+"/storeraw", "application/json", reqBodyBuf) + if err != nil { + return common.EncryptedPayloadHash{}, fmt.Errorf("unable to invoke /storeraw due to %s", err) + } + defer func() { + _ = resp.Body.Close() + }() + if resp.StatusCode != http.StatusOK { + return common.EncryptedPayloadHash{}, fmt.Errorf("server returns %s", resp.Status) + } + // parse response + var storeRawResp storeRawResp + if err := json.NewDecoder(resp.Body).Decode(&storeRawResp); err != nil { + return common.EncryptedPayloadHash{}, err + } + encryptedPayloadHash, err := common.Base64ToEncryptedPayloadHash(storeRawResp.Key) + if err != nil { + return common.EncryptedPayloadHash{}, err + } + return encryptedPayloadHash, nil +} diff --git a/ethclient/privateTransactionManagerClient_test.go b/ethclient/privateTransactionManagerClient_test.go new file mode 100644 index 0000000000..b1b6d53f5d --- /dev/null +++ b/ethclient/privateTransactionManagerClient_test.go @@ -0,0 +1,56 @@ +package ethclient + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +const ( + arbitraryBase64Data = "YXJiaXRyYXJ5IGRhdGE=" // = "arbitrary data" +) + +func TestPrivateTransactionManagerClient_storeRaw(t *testing.T) { + // mock tessera client + expectedData := []byte("arbitrary data") + expectedDataEPH := common.BytesToEncryptedPayloadHash(expectedData) + arbitraryServer := newStoreRawServer() + defer arbitraryServer.Close() + testObject, err := newPrivateTransactionManagerClient(arbitraryServer.URL) + assert.NoError(t, err) + + key, err := testObject.StoreRaw([]byte("arbitrary payload"), "arbitrary private from") + + assert.NoError(t, err) + assert.Equal(t, expectedDataEPH, key) +} + +func newStoreRawServer() *httptest.Server { + arbitraryResponse := fmt.Sprintf(` +{ + "key": "%s" +} +`, arbitraryBase64Data) + mux := http.NewServeMux() + mux.HandleFunc("/storeraw", func(w http.ResponseWriter, req *http.Request) { + if req.Method == "POST" { + // parse request + var storeRawReq storeRawReq + if err := json.NewDecoder(req.Body).Decode(&storeRawReq); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + // send response + _, _ = fmt.Fprintf(w, "%s", arbitraryResponse) + } else { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + } + + }) + return httptest.NewServer(mux) +} diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index b60ac56eab..33b772b471 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -406,7 +406,9 @@ func (s *Service) login(conn *websocket.Conn) error { infos := s.server.NodeInfo() var network, protocol string - if info := infos.Protocols["eth"]; info != nil { + //must pass engine protocol name for quorum + p := s.engine.Protocol() + if info := infos.Protocols[p.Name]; info != nil { network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) } else { diff --git a/extension/api.go b/extension/api.go new file mode 100644 index 0000000000..e197f85639 --- /dev/null +++ b/extension/api.go @@ -0,0 +1,385 @@ +package extension + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/multitenancy" + "github.com/ethereum/go-ethereum/permission/core" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errNotAcceptor = errors.New("account is not acceptor of this extension request") + errNotCreator = errors.New("account is not the creator of this extension request") +) + +const extensionCompleted = "DONE" +const extensionInProgress = "ACTIVE" + +type PrivateExtensionAPI struct { + privacyService *PrivacyService +} + +func NewPrivateExtensionAPI(privacyService *PrivacyService) *PrivateExtensionAPI { + return &PrivateExtensionAPI{ + privacyService: privacyService, + } +} + +// ActiveExtensionContracts returns the list of all currently outstanding extension contracts +func (api *PrivateExtensionAPI) ActiveExtensionContracts() []ExtensionContract { + api.privacyService.mu.Lock() + defer api.privacyService.mu.Unlock() + + extracted := make([]ExtensionContract, 0, len(api.privacyService.currentContracts)) + for _, contract := range api.privacyService.currentContracts { + extracted = append(extracted, *contract) + } + return extracted +} + +// checks of the passed contract address is under extension process +func (api *PrivateExtensionAPI) checkIfContractUnderExtension(toExtend common.Address) bool { + for _, v := range api.ActiveExtensionContracts() { + if v.ContractExtended == toExtend { + return true + } + } + return false +} + +// checks if the voter has already voted on the contract. +func (api *PrivateExtensionAPI) checkAlreadyVoted(addressToVoteOn, from common.Address) bool { + caller, _ := api.privacyService.managementContractFacade.Caller(addressToVoteOn) + opts := bind.CallOpts{Pending: true, From: from} + + voted, _ := caller.CheckIfVoted(&opts) + return voted +} + +// checks if the voter has already voted on the contract. +func (api *PrivateExtensionAPI) checkIfExtensionComplete(addressToVoteOn, from common.Address) (bool, error) { + caller, _ := api.privacyService.managementContractFacade.Caller(addressToVoteOn) + opts := bind.CallOpts{Pending: true, From: from} + + status, err := caller.CheckIfExtensionFinished(&opts) + if err != nil { + return true, err + } + return status, nil +} + +// returns the contract being extended for the given management contract +func (api *PrivateExtensionAPI) getContractExtended(addressToVoteOn, from common.Address) (common.Address, error) { + caller, _ := api.privacyService.managementContractFacade.Caller(addressToVoteOn) + opts := bind.CallOpts{Pending: true, From: from} + + return caller.ContractToExtend(&opts) +} + +// checks if the contract being extended is a public contract +func (api *PrivateExtensionAPI) checkIfPublicContract(toExtend common.Address) bool { + // check if the passed contract is public contract + publicStateDb, _, _ := api.privacyService.stateFetcher.chainAccessor.State() + if publicStateDb != nil && publicStateDb.Exist(toExtend) { + return true + } + return false +} + +// checks if the contract being extended is available on the node +func (api *PrivateExtensionAPI) checkIfPrivateStateExists(toExtend common.Address) bool { + // check if the private contract exists on the node extending the contract + _, privateStateDb, _ := api.privacyService.stateFetcher.chainAccessor.State() + if privateStateDb != nil { + if privateStateDb.GetCode(toExtend) != nil { + return true + } + } + return false +} + +func (api *PrivateExtensionAPI) doMultiTenantChecks(ctx context.Context, address common.Address, txa ethapi.SendTxArgs) error { + apiHelper := api.privacyService.apiBackendHelper + if authToken, ok := apiHelper.SupportsMultitenancy(ctx); ok { + if len(txa.PrivateFrom) == 0 { + return errors.New("You must specify 'privateFrom' when running in a multitenant node") + } + // check whether the user has access to txa.PrivateFrom and the txa.From eth account + attributes := multitenancy.FullAccessContractSecurityAttributes(txa.From, txa.PrivateFrom) + chainAccessor := api.privacyService.stateFetcher.chainAccessor + currentBlock := chainAccessor.CurrentBlock().Number().Int64() + extraDataReader, err := apiHelper.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(currentBlock)) + if err != nil { + return fmt.Errorf("no account extra data reader at block %v: %w", currentBlock, err) + } + + managedParties, err := extraDataReader.GetManagedParties(address) + if err != nil { + return err + } + attributes = append(attributes, + multitenancy.NewContractSecurityAttributeBuilder().FromEOA(txa.From).Private().Write().Parties(managedParties).Build(), + multitenancy.NewContractSecurityAttributeBuilder().FromEOA(txa.From).Private().Read().Parties(managedParties).Build()) + + if authorized, _ := apiHelper.IsAuthorized(ctx, authToken, attributes...); !authorized { + return multitenancy.ErrNotAuthorized + } + } + return nil +} + +// ApproveContractExtension submits the vote to the specified extension management contract. The vote indicates whether to extend +// a given contract to a new participant or not +func (api *PrivateExtensionAPI) ApproveExtension(ctx context.Context, addressToVoteOn common.Address, vote bool, txa ethapi.SendTxArgs) (string, error) { + err := api.doMultiTenantChecks(ctx, addressToVoteOn, txa) + if err != nil { + return "", err + } + // check if the extension has been completed. if yes + // no acceptance required + status, err := api.checkIfExtensionComplete(addressToVoteOn, txa.From) + if err != nil { + return "", err + } + + if status { + return "", errors.New("contract extension process complete. nothing to accept") + } + + if !core.CheckIfAdminAccount(txa.From) { + return "", errors.New("account cannot accept extension") + } + + toExtend, err := api.getContractExtended(addressToVoteOn, txa.From) + if err != nil { + return "", err + } + + // get all participants for the contract being extended + participants, err := api.privacyService.GetAllParticipants(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) + if err == nil { + txa.PrivateFor = append(txa.PrivateFor, participants...) + } + + txArgs, err := api.privacyService.GenerateTransactOptions(txa) + if err != nil { + return "", err + } + + voterList, err := api.privacyService.managementContractFacade.GetAllVoters(addressToVoteOn) + if err != nil { + return "", err + } + if isVoter := checkAddressInList(txArgs.From, voterList); !isVoter { + return "", errNotAcceptor + } + + if api.checkAlreadyVoted(addressToVoteOn, txArgs.From) { + return "", errors.New("already voted") + } + uuid, err := generateUuid(addressToVoteOn, txArgs.PrivateFrom, txArgs.PrivateFor, api.privacyService.ptm) + if err != nil { + return "", err + } + + //Find the extension contract in order to interact with it + extender, err := api.privacyService.managementContractFacade.Transactor(addressToVoteOn) + if err != nil { + return "", err + } + + //Perform the vote transaction. + tx, err := extender.DoVote(txArgs, vote, uuid) + if err != nil { + return "", err + } + msg := fmt.Sprintf("0x%x", tx.Hash()) + return msg, nil +} + +// ExtendContract deploys a new extension management contract to the blockchain to start the process of extending +// a contract to a new participant +//Create a new extension contract that signifies that we want to add a new participant to an existing contract +//This should contain: +// - arguments for sending a new transaction (the same as sendTransaction) +// - the contract address we want to extend +// - the new PTM public key +// - the Ethereum addresses of who can vote to extend the contract +func (api *PrivateExtensionAPI) ExtendContract(ctx context.Context, toExtend common.Address, newRecipientPtmPublicKey string, recipientAddr common.Address, txa ethapi.SendTxArgs) (string, error) { + // check if the contract to be extended is already under extension + // if yes throw an error + if api.checkIfContractUnderExtension(toExtend) { + return "", errors.New("contract extension in progress for the given contract address") + } + + // check if a public contract is being extended + if api.checkIfPublicContract(toExtend) { + return "", errors.New("extending a public contract!!! not allowed") + } + + // check if a public contract is being extended + if !api.checkIfPrivateStateExists(toExtend) { + return "", errors.New("extending a non-existent private contract!!! not allowed") + } + + err := api.doMultiTenantChecks(ctx, toExtend, txa) + if err != nil { + return "", err + } + + // check if recipient address is 0x0 + if recipientAddr == (common.Address{0}) { + return "", errors.New("invalid recipient address") + } + + // check if contract creator + if !api.privacyService.CheckIfContractCreator(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) { + return "", errors.New("operation not allowed") + } + + // if running in permissioned mode with new permissions model + // ensure that the account extending the contract is an admin + // account and recipient account is an admin account as well + if txa.From == recipientAddr { + return "", errors.New("account accepting the extension cannot be the account initiating extension") + } + if !core.CheckIfAdminAccount(txa.From) { + return "", errors.New("account not an org admin account, cannot initiate extension") + } + if !core.CheckIfAdminAccount(recipientAddr) { + return "", errors.New("recipient account address is not an org admin account. cannot accept extension") + } + + // check the new key is valid + if _, err := base64.StdEncoding.DecodeString(newRecipientPtmPublicKey); err != nil { + return "", errors.New("invalid new recipient transaction manager key provided") + } + + // check the the intended new recipient will actually receive the extension request + switch len(txa.PrivateFor) { + case 0: + txa.PrivateFor = append(txa.PrivateFor, newRecipientPtmPublicKey) + case 1: + if txa.PrivateFor[0] != newRecipientPtmPublicKey { + return "", errors.New("mismatch between recipient transaction manager key and privateFor argument") + } + default: + return "", errors.New("invalid transaction manager keys given in privateFor argument") + } + + // get all participants for the contract being extended + participants, err := api.privacyService.GetAllParticipants(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) + if err == nil { + txa.PrivateFor = append(txa.PrivateFor, participants...) + } + + //generate some valid transaction options for sending in the transaction + txArgs, err := api.privacyService.GenerateTransactOptions(txa) + if err != nil { + return "", err + } + + //Deploy the contract + tx, err := api.privacyService.managementContractFacade.Deploy(txArgs, toExtend, recipientAddr, newRecipientPtmPublicKey) + if err != nil { + return "", err + } + + //Return the transaction hash for later lookup + msg := fmt.Sprintf("0x%x", tx.Hash()) + return msg, nil +} + +// CancelExtension allows the creator to cancel the given extension contract, ensuring +// that no more calls for votes or accepting can be made +func (api *PrivateExtensionAPI) CancelExtension(ctx context.Context, extensionContract common.Address, txa ethapi.SendTxArgs) (string, error) { + err := api.doMultiTenantChecks(ctx, extensionContract, txa) + if err != nil { + return "", err + } + + status, err := api.checkIfExtensionComplete(extensionContract, txa.From) + if err != nil { + return "", err + } + if status { + return "", errors.New("contract extension process complete. nothing to cancel") + } + + toExtend, err := api.getContractExtended(extensionContract, txa.From) + if err != nil { + return "", err + } + + // get all participants for the contract being extended + participants, err := api.privacyService.GetAllParticipants(api.privacyService.stateFetcher.getCurrentBlockHash(), toExtend) + if err == nil { + txa.PrivateFor = append(txa.PrivateFor, participants...) + } + + txArgs, err := api.privacyService.GenerateTransactOptions(txa) + if err != nil { + return "", err + } + + caller, err := api.privacyService.managementContractFacade.Caller(extensionContract) + if err != nil { + return "", err + } + creatorAddress, err := caller.Creator(nil) + if err != nil { + return "", err + } + if isCreator := checkAddressInList(txArgs.From, []common.Address{creatorAddress}); !isCreator { + return "", errNotCreator + } + + extender, err := api.privacyService.managementContractFacade.Transactor(extensionContract) + if err != nil { + return "", err + } + + tx, err := extender.Finish(txArgs) + if err != nil { + return "", err + } + msg := fmt.Sprintf("0x%x", tx.Hash()) + return msg, nil +} + +// Returns the extension status from management contract +func (api *PrivateExtensionAPI) GetExtensionStatus(ctx context.Context, extensionContract common.Address) (string, error) { + apiHelper := api.privacyService.apiBackendHelper + if authToken, ok := apiHelper.SupportsMultitenancy(ctx); ok { + currentBlock := apiHelper.CurrentBlock().Number().Int64() + extraDataReader, err := apiHelper.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(currentBlock)) + if err != nil { + return "", fmt.Errorf("no account extra data reader at block %v: %w", currentBlock, err) + } + managedParties, err := extraDataReader.GetManagedParties(extensionContract) + if err != nil { + return "", err + } + if authorized, _ := apiHelper.IsAuthorized(ctx, authToken, + multitenancy.NewContractSecurityAttributeBuilder().Private().Read().Parties(managedParties).Build()); !authorized { + return "", multitenancy.ErrNotAuthorized + } + } + status, err := api.checkIfExtensionComplete(extensionContract, common.Address{}) + if err != nil { + return "", err + } + + if status { + return extensionCompleted, nil + } + + return extensionInProgress, nil +} diff --git a/extension/backend.go b/extension/backend.go new file mode 100644 index 0000000000..c10ac96b1e --- /dev/null +++ b/extension/backend.go @@ -0,0 +1,437 @@ +package extension + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/extension/extensionContracts" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/rpc" +) + +type PrivacyService struct { + ptm private.PrivateTransactionManager + stateFetcher *StateFetcher + accountManager *accounts.Manager + dataHandler DataHandler + managementContractFacade ManagementContractFacade + extClient Client + stopFeed event.Feed + apiBackendHelper APIBackendHelper + + mu sync.Mutex + currentContracts map[common.Address]*ExtensionContract +} + +var ( + //default gas limit to use if not passed in sendTxArgs + defaultGasLimit = uint64(4712384) + //default gas price to use if not passed in sendTxArgs + defaultGasPrice = big.NewInt(0) + + //Private participants must be specified for contract extension related transactions + errNotPrivate = errors.New("must specify private participants") +) + +// to signal all watches when service is stopped +type stopEvent struct { +} + +func (service *PrivacyService) subscribeStopEvent() (chan stopEvent, event.Subscription) { + c := make(chan stopEvent) + s := service.stopFeed.Subscribe(c) + return c, s +} + +func New(ptm private.PrivateTransactionManager, manager *accounts.Manager, handler DataHandler, fetcher *StateFetcher, apiBackendHelper APIBackendHelper) (*PrivacyService, error) { + service := &PrivacyService{ + currentContracts: make(map[common.Address]*ExtensionContract), + ptm: ptm, + dataHandler: handler, + stateFetcher: fetcher, + accountManager: manager, + apiBackendHelper: apiBackendHelper, + } + + var err error + service.currentContracts, err = service.dataHandler.Load() + if err != nil { + return nil, errors.New("could not load existing extension contracts: " + err.Error()) + } + + return service, nil +} + +func (service *PrivacyService) initialise(node *node.Node) { + service.mu.Lock() + defer service.mu.Unlock() + + rpcClient, err := node.Attach() + if err != nil { + panic("extension: could not connect to ethereum client rpc") + } + + client := ethclient.NewClientWithPTM(rpcClient, service.ptm) + service.managementContractFacade = NewManagementContractFacade(client) + service.extClient = NewInProcessClient(client) + + for _, f := range []func() error{ + service.watchForNewContracts, // watch for new extension contract creation event + service.watchForCancelledContracts, // watch for extension contract cancellation event + service.watchForCompletionEvents, // watch for extension contract voting complete event + } { + if err := f(); err != nil { + log.Error("") + } + } + +} + +func (service *PrivacyService) watchForNewContracts() error { + incomingLogs, subscription, err := service.extClient.SubscribeToLogs(newExtensionQuery) + + if err != nil { + return err + } + + go func() { + stopChan, stopSubscription := service.subscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case err := <-subscription.Err(): + log.Error("Contract extension watcher subscription error", "error", err) + break + + case foundLog := <-incomingLogs: + service.mu.Lock() + + tx, _ := service.extClient.TransactionByHash(foundLog.TxHash) + from, _ := types.QuorumPrivateTxSigner{}.Sender(tx) + + newExtensionEvent, err := extensionContracts.UnpackNewExtensionCreatedLog(foundLog.Data) + if err != nil { + log.Error("Error unpacking extension creation log", "error", err) + log.Debug("Errored log", foundLog) + service.mu.Unlock() + continue + } + + newContractExtension := ExtensionContract{ + ContractExtended: newExtensionEvent.ToExtend, + Initiator: from, + Recipient: newExtensionEvent.RecipientAddress, + RecipientPtmKey: newExtensionEvent.RecipientPTMKey, + ManagementContractAddress: foundLog.Address, + CreationData: tx.Data(), + } + + service.currentContracts[foundLog.Address] = &newContractExtension + err = service.dataHandler.Save(service.currentContracts) + if err != nil { + log.Error("Error writing extension data to file", "error", err) + service.mu.Unlock() + continue + } + service.mu.Unlock() + + // if party is sender then complete self voting + data := common.BytesToEncryptedPayloadHash(newContractExtension.CreationData) + isSender, _ := service.ptm.IsSender(data) + + if isSender { + fetchedParties, err := service.ptm.GetParticipants(data) + if err != nil || len(fetchedParties) == 0 { + log.Error("Extension: unable to fetch all parties for extension management contract", "error", err) + continue + } + + privateFrom, _, _, _, err := service.ptm.Receive(data) + if err != nil || len(privateFrom) == 0 { + log.Error("Extension: unable to fetch privateFrom(sender) for extension management contract", "error", err) + continue + } + + //Find the extension contract in order to interact with it + caller, _ := service.managementContractFacade.Caller(newContractExtension.ManagementContractAddress) + contractCreator, _ := caller.Creator(nil) + + txArgs := ethapi.SendTxArgs{From: contractCreator, PrivateTxArgs: ethapi.PrivateTxArgs{PrivateFor: fetchedParties, PrivateFrom: privateFrom}} + + extensionAPI := NewPrivateExtensionAPI(service) + _, err = extensionAPI.ApproveExtension(context.Background(), newContractExtension.ManagementContractAddress, true, txArgs) + + if err != nil { + log.Error("Extension: initiator vote on management contract failed", "error", err) + } + } + + case <-stopChan: + return + } + } + }() + + return nil +} + +func (service *PrivacyService) watchForCancelledContracts() error { + incomingLogs, subscription, err := service.extClient.SubscribeToLogs(finishedExtensionQuery) + + if err != nil { + return err + } + + go func() { + stopChan, stopSubscription := service.subscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case err := <-subscription.Err(): + log.Error("Contract cancellation extension watcher subscription error", "error", err) + return + case l := <-incomingLogs: + service.mu.Lock() + if _, ok := service.currentContracts[l.Address]; ok { + delete(service.currentContracts, l.Address) + if err := service.dataHandler.Save(service.currentContracts); err != nil { + log.Error("Faile to store list of contracts being extended", "error", err) + } + } + service.mu.Unlock() + case <-stopChan: + return + } + } + + }() + + return nil +} + +func (service *PrivacyService) watchForCompletionEvents() error { + incomingLogs, _, err := service.extClient.SubscribeToLogs(canPerformStateShareQuery) + + if err != nil { + return err + } + + go func() { + stopChan, stopSubscription := service.subscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case l := <-incomingLogs: + log.Debug("Extension: Received a completion event", "address", l.Address.Hex(), "blockNumber", l.BlockNumber) + service.mu.Lock() + func() { + defer func() { + service.mu.Unlock() + }() + extensionEntry, ok := service.currentContracts[l.Address] + if !ok { + // we didn't have this management contract, so ignore it + log.Debug("Extension: this node doesn't participate in the contract extender", "address", l.Address.Hex()) + return + } + + //Find the extension contract in order to interact with it + caller, err := service.managementContractFacade.Caller(l.Address) + if err != nil { + log.Error("service.managementContractFacade.Caller", "address", l.Address.Hex(), "error", err) + return + } + contractCreator, err := caller.Creator(nil) + if err != nil { + log.Error("[contract] caller.Creator", "error", err) + return + } + log.Debug("Extension: check if this node has the account that created the contract extender", "account", contractCreator) + if _, err := service.accountManager.Find(accounts.Account{Address: contractCreator}); err != nil { + log.Warn("Account used to sign extension contract no longer available", "account", contractCreator.Hex()) + return + } + + // fetch all the participants and send + payload := common.BytesToEncryptedPayloadHash(extensionEntry.CreationData) + fetchedParties, err := service.ptm.GetParticipants(payload) + if err != nil || len(fetchedParties) == 0 { + log.Error("Extension: Unable to fetch all parties for extension management contract", "error", err) + return + } + log.Debug("Extension: able to fetch all parties", "parties", fetchedParties) + + privateFrom, _, _, _, err := service.ptm.Receive(payload) + if err != nil || len(privateFrom) == 0 { + log.Error("Extension: unable to fetch privateFrom(sender) for extension management contract", "error", err) + return + } + log.Debug("Extension: able to fetch privateFrom(sender)", "privateFrom", privateFrom) + + txArgs, err := service.GenerateTransactOptions(ethapi.SendTxArgs{From: contractCreator, PrivateTxArgs: ethapi.PrivateTxArgs{PrivateFor: fetchedParties, PrivateFrom: privateFrom}}) + if err != nil { + log.Error("service.accountManager.GenerateTransactOptions", "error", err, "contractCreator", contractCreator.Hex(), "privateFor", fetchedParties) + return + } + + //we found the account, so we can send + contractToExtend, err := caller.ContractToExtend(nil) + if err != nil { + log.Error("[contract] caller.ContractToExtend", "error", err) + return + } + log.Debug("Extension: dump current state", "block", l.BlockHash, "contract", contractToExtend.Hex()) + entireStateData, err := service.stateFetcher.GetAddressStateFromBlock(l.BlockHash, contractToExtend) + if err != nil { + log.Error("[state] service.stateFetcher.GetAddressStateFromBlock", "block", l.BlockHash.Hex(), "contract", contractToExtend.Hex(), "error", err) + return + } + + log.Debug("Extension: send the state dump to the new recipient", "recipients", fetchedParties) + + // PSV & PP changes + // send the new transaction with state dump to all participants + extraMetaData := engine.ExtraMetadata{PrivacyFlag: engine.PrivacyFlagStandardPrivate} + privacyMetaData, err := service.stateFetcher.GetPrivacyMetaData(l.BlockHash, contractToExtend) + if err != nil { + log.Error("[privacyMetaData] fetch err", "err", err) + } else { + extraMetaData.PrivacyFlag = privacyMetaData.PrivacyFlag + if privacyMetaData.PrivacyFlag == engine.PrivacyFlagStateValidation { + storageRoot, err := service.stateFetcher.GetStorageRoot(l.BlockHash, contractToExtend) + if err != nil { + log.Error("[storageRoot] fetch err", "err", err) + } + extraMetaData.ACMerkleRoot = storageRoot + } + } + _, _, hashOfStateData, err := service.ptm.Send(entireStateData, privateFrom, fetchedParties, &extraMetaData) + + if err != nil { + log.Error("[ptm] service.ptm.Send", "stateDataInHex", hex.EncodeToString(entireStateData[:]), "recipients", fetchedParties, "error", err) + return + } + hashofStateDataBase64 := hashOfStateData.ToBase64() + + transactor, err := service.managementContractFacade.Transactor(l.Address) + if err != nil { + log.Error("service.managementContractFacade.Transactor", "address", l.Address.Hex(), "error", err) + return + } + log.Debug("Extension: store the encrypted payload hash of dump state", "contract", l.Address.Hex()) + if tx, err := transactor.SetSharedStateHash(txArgs, hashofStateDataBase64); err != nil { + log.Error("[contract] transactor.SetSharedStateHash", "error", err, "hashOfStateInBase64", hashofStateDataBase64) + } else { + log.Debug("Extension: transaction carrying shared state", "txhash", tx.Hash(), "private", tx.IsPrivate()) + } + }() + case <-stopChan: + return + } + } + + }() + return nil +} + +// node.Service interface methods: +func (service *PrivacyService) Protocols() []p2p.Protocol { + return []p2p.Protocol{} +} + +func (service *PrivacyService) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "quorumExtension", + Version: "1.0", + Service: NewPrivateExtensionAPI(service), + Public: true, + }, + } +} + +func (service *PrivacyService) Start(p2pServer *p2p.Server) error { + log.Debug("extension service: starting") + return nil +} + +func (service *PrivacyService) Stop() error { + log.Info("extension service: stopping") + service.stopFeed.Send(stopEvent{}) + log.Info("extension service: stopped") + return nil +} + +func (service *PrivacyService) GenerateTransactOptions(txa ethapi.SendTxArgs) (*bind.TransactOpts, error) { + if txa.PrivateFor == nil { + return nil, errNotPrivate + } + from := accounts.Account{Address: txa.From} + wallet, err := service.accountManager.Find(from) + + if err != nil { + return nil, fmt.Errorf("no wallet found for account %s", txa.From.String()) + } + + //Find the account we plan to send the transaction from + + txArgs := bind.NewWalletTransactor(wallet, from) + txArgs.PrivateFrom = txa.PrivateFrom + txArgs.PrivateFor = txa.PrivateFor + txArgs.GasLimit = defaultGasLimit + txArgs.GasPrice = defaultGasPrice + if txa.GasPrice != nil { + txArgs.GasPrice = txa.GasPrice.ToInt() + } + if txa.Gas != nil { + txArgs.GasLimit = uint64(*txa.Gas) + } + return txArgs, nil +} + +// returns the participant list for a given private contract +func (service *PrivacyService) GetAllParticipants(blockHash common.Hash, address common.Address) ([]string, error) { + privacyMetaData, err := service.stateFetcher.GetPrivacyMetaData(blockHash, address) + if err != nil { + return nil, err + } + if privacyMetaData.PrivacyFlag.IsStandardPrivate() { + return nil, nil + } + + participants, err := service.ptm.GetParticipants(privacyMetaData.CreationTxHash) + if err != nil { + return nil, err + } + return participants, nil +} + +// check if the node had created the contract +func (service *PrivacyService) CheckIfContractCreator(blockHash common.Hash, address common.Address) bool { + privacyMetaData, err := service.stateFetcher.GetPrivacyMetaData(blockHash, address) + if err != nil { + return true + } + + isCreator, err := service.ptm.IsSender(privacyMetaData.CreationTxHash) + if err != nil { + return false + } + + return isCreator +} diff --git a/extension/backend_test.go b/extension/backend_test.go new file mode 100644 index 0000000000..0bd1c35b29 --- /dev/null +++ b/extension/backend_test.go @@ -0,0 +1,211 @@ +package extension + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" +) + +type MockBackend struct { + wallets []accounts.Wallet +} + +func (backend *MockBackend) Wallets() []accounts.Wallet { + return backend.wallets +} + +func (backend *MockBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + return nil +} + +type MockWallet struct { + isContained bool +} + +func (wallet *MockWallet) URL() accounts.URL { panic("not implemented") } + +func (wallet *MockWallet) Status() (string, error) { panic("not implemented") } + +func (wallet *MockWallet) Open(passphrase string) error { panic("not implemented") } + +func (wallet *MockWallet) Close() error { panic("not implemented") } + +func (wallet *MockWallet) Accounts() []accounts.Account { panic("not implemented") } + +func (wallet *MockWallet) Contains(account accounts.Account) bool { return wallet.isContained } + +func (wallet *MockWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + panic("not implemented") +} + +func (wallet *MockWallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + panic("not implemented") +} + +func (wallet *MockWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + panic("not implemented") +} + +func (wallet *MockWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + panic("not implemented") +} + +func (wallet *MockWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + panic("not implemented") +} + +func (wallet *MockWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + panic("not implemented") +} + +func (wallet *MockWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + panic("not implemented") +} + +func (wallet *MockWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { + panic("not implemented") +} + +func TestGenerateTransactionOptionsErrorsWhenNoPrivateParticipants(t *testing.T) { + sendTxArgs := ethapi.SendTxArgs{ + From: common.Address{}, + } + + mockBackend := MockBackend{} + accountManager := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: true}, &mockBackend) + + service := &PrivacyService{ + accountManager: accountManager, + } + + _, err := service.GenerateTransactOptions(sendTxArgs) + if err == nil { + t.Errorf("expected err to not be nil") + return + } + + expectedErr := "must specify private participants" + if err.Error() != expectedErr { + t.Errorf("expected err to be '%s', but was '%s'", expectedErr, err.Error()) + } +} + +func TestGenerateTransactionOptionsErrorsWhenAccountNotFound(t *testing.T) { + privateTxArgs := ethapi.PrivateTxArgs{PrivateFor: []string{}} + sendTxArgs := ethapi.SendTxArgs{ + From: common.Address{}, + PrivateTxArgs: privateTxArgs, + } + + mockBackend := MockBackend{} + accountManager := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: true}, &mockBackend) + + service := &PrivacyService{ + accountManager: accountManager, + } + + _, err := service.GenerateTransactOptions(sendTxArgs) + if err == nil { + t.Errorf("expected err to not be nil") + return + } + + expectedErr := "no wallet found for account 0x0000000000000000000000000000000000000000" + if err.Error() != expectedErr { + t.Errorf("expected err to be '%s', but was '%s'", expectedErr, err.Error()) + } +} + +func TestGenerateTransactionOptionsGivesDefaults(t *testing.T) { + from := common.HexToAddress("0x2222222222222222222222222222222222222222") + + privateTxArgs := ethapi.PrivateTxArgs{PrivateFor: []string{"privateFor1", "privateFor2"}, PrivateFrom: "privateFrom"} + + sendTxArgs := ethapi.SendTxArgs{ + From: from, + PrivateTxArgs: privateTxArgs, + } + + mockWallet := &MockWallet{isContained: true} + mockBackend := MockBackend{wallets: []accounts.Wallet{mockWallet}} + accountManager := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: true}, &mockBackend) + service := &PrivacyService{ + accountManager: accountManager, + } + + generatedOptions, err := service.GenerateTransactOptions(sendTxArgs) + if err != nil { + t.Errorf("expected err to be '%s', but was '%s'", "nil", err.Error()) + return + } + + if generatedOptions.PrivateFrom != sendTxArgs.PrivateFrom { + t.Errorf("expected PrivateFrom to be '%s', but was '%s'", sendTxArgs.PrivateFrom, generatedOptions.PrivateFrom) + return + } + + if len(generatedOptions.PrivateFor) != 2 || generatedOptions.PrivateFor[0] != sendTxArgs.PrivateFor[0] || generatedOptions.PrivateFor[1] != sendTxArgs.PrivateFor[1] { + t.Errorf("expected PrivateFor to be '%s', but was '%s'", sendTxArgs.PrivateFor, generatedOptions.PrivateFor) + return + } + + if generatedOptions.GasLimit != 4712384 { + t.Errorf("expected GasLimit to be '%d', but was '%d'", 4712384, generatedOptions.GasLimit) + return + } + + if generatedOptions.GasPrice == nil || generatedOptions.GasPrice.Cmp(new(big.Int)) != 0 { + t.Errorf("expected GasLimit to be '%d', but was '%d'", new(big.Int), generatedOptions.GasPrice) + return + } + + if generatedOptions.From != from { + t.Errorf("expected From to be '%d', but was '%d'", from, generatedOptions.From) + return + } +} + +func TestGenerateTransactionOptionsGivesNonDefaultsWhenSpecified(t *testing.T) { + from := common.HexToAddress("0x2222222222222222222222222222222222222222") + gasLimit := hexutil.Uint64(5000) + gasPrice := hexutil.Big(*big.NewInt(50)) + + privateTxArgs := ethapi.PrivateTxArgs{PrivateFor: []string{}} + + sendTxArgs := ethapi.SendTxArgs{ + From: from, + Gas: &gasLimit, + GasPrice: &gasPrice, + PrivateTxArgs: privateTxArgs, + } + + mockWallet := &MockWallet{isContained: true} + mockBackend := MockBackend{wallets: []accounts.Wallet{mockWallet}} + accountManager := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: true}, &mockBackend) + service := &PrivacyService{ + accountManager: accountManager, + } + + generatedOptions, err := service.GenerateTransactOptions(sendTxArgs) + if err != nil { + t.Errorf("expected err to be '%s', but was '%s'", "nil", err.Error()) + return + } + + if generatedOptions.GasLimit != 5000 { + t.Errorf("expected GasLimit to be '%d', but was '%d'", 5000, generatedOptions.GasLimit) + return + } + + if generatedOptions.GasPrice == nil || generatedOptions.GasPrice.Cmp(big.NewInt(50)) != 0 { + t.Errorf("expected GasLimit to be '%d', but was '%d'", big.NewInt(50), generatedOptions.GasPrice) + return + } +} diff --git a/extension/client.go b/extension/client.go new file mode 100644 index 0000000000..075c9c9daf --- /dev/null +++ b/extension/client.go @@ -0,0 +1,46 @@ +package extension + +import ( + "context" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +type Client interface { + SubscribeToLogs(query ethereum.FilterQuery) (<-chan types.Log, ethereum.Subscription, error) + NextNonce(from common.Address) (uint64, error) + TransactionByHash(hash common.Hash) (*types.Transaction, error) + TransactionInBlock(blockHash common.Hash, txIndex uint) (*types.Transaction, error) +} + +type InProcessClient struct { + client *ethclient.Client +} + +func NewInProcessClient(client *ethclient.Client) *InProcessClient { + return &InProcessClient{ + client: client, + } +} + +func (client *InProcessClient) SubscribeToLogs(query ethereum.FilterQuery) (<-chan types.Log, ethereum.Subscription, error) { + retrievedLogsChan := make(chan types.Log) + sub, err := client.client.SubscribeFilterLogs(context.Background(), query, retrievedLogsChan) + return retrievedLogsChan, sub, err +} + +func (client *InProcessClient) NextNonce(from common.Address) (uint64, error) { + return client.client.PendingNonceAt(context.Background(), from) +} + +func (client *InProcessClient) TransactionByHash(hash common.Hash) (*types.Transaction, error) { + tx, _, err := client.client.TransactionByHash(context.Background(), hash) + return tx, err +} + +func (client *InProcessClient) TransactionInBlock(blockHash common.Hash, txIndex uint) (*types.Transaction, error) { + return client.client.TransactionInBlock(context.Background(), blockHash, txIndex) +} diff --git a/extension/contract_facade.go b/extension/contract_facade.go new file mode 100644 index 0000000000..2f7f807559 --- /dev/null +++ b/extension/contract_facade.go @@ -0,0 +1,61 @@ +package extension + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/extension/extensionContracts" +) + +type ManagementContractFacade interface { + Transactor(managementAddress common.Address) (*extensionContracts.ContractExtenderTransactor, error) + Caller(managementAddress common.Address) (*extensionContracts.ContractExtenderCaller, error) + Deploy(args *bind.TransactOpts, toExtend common.Address, recipientAddress common.Address, recipientHash string) (*types.Transaction, error) + + GetAllVoters(addressToVoteOn common.Address) ([]common.Address, error) +} + +type EthclientManagementContractFacade struct { + client *ethclient.Client +} + +func NewManagementContractFacade(client *ethclient.Client) ManagementContractFacade { + return EthclientManagementContractFacade{client: client} +} + +func (facade EthclientManagementContractFacade) Transactor(managementAddress common.Address) (*extensionContracts.ContractExtenderTransactor, error) { + return extensionContracts.NewContractExtenderTransactor(managementAddress, facade.client) +} + +func (facade EthclientManagementContractFacade) Caller(managementAddress common.Address) (*extensionContracts.ContractExtenderCaller, error) { + return extensionContracts.NewContractExtenderCaller(managementAddress, facade.client) +} + +func (facade EthclientManagementContractFacade) Deploy(args *bind.TransactOpts, toExtend common.Address, recipientAddress common.Address, recipientHash string) (*types.Transaction, error) { + _, tx, _, err := extensionContracts.DeployContractExtender(args, facade.client, toExtend, recipientAddress, recipientHash) + return tx, err +} + +func (facade EthclientManagementContractFacade) GetAllVoters(addressToVoteOn common.Address) ([]common.Address, error) { + caller, err := facade.Caller(addressToVoteOn) + if err != nil { + return nil, err + } + numberOfVoters, err := caller.TotalNumberOfVoters(nil) + if err != nil { + return nil, err + } + var i int64 + var voters []common.Address + for i = 0; i < numberOfVoters.Int64(); i++ { + voter, err := caller.WalletAddressesToVote(nil, big.NewInt(i)) + if err != nil { + return nil, err + } + voters = append(voters, voter) + } + return voters, nil +} diff --git a/extension/data_handler.go b/extension/data_handler.go new file mode 100644 index 0000000000..b6d078f3ac --- /dev/null +++ b/extension/data_handler.go @@ -0,0 +1,55 @@ +package extension + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +const extensionContractData = "activeExtensions.json" + +type DataHandler interface { + Load() (map[common.Address]*ExtensionContract, error) + + Save(extensionContracts map[common.Address]*ExtensionContract) error +} + +type JsonFileDataHandler struct { + saveFile string +} + +func NewJsonFileDataHandler(dataDirectory string) *JsonFileDataHandler { + return &JsonFileDataHandler{ + saveFile: filepath.Join(dataDirectory, extensionContractData), + } +} + +func (handler *JsonFileDataHandler) Load() (map[common.Address]*ExtensionContract, error) { + currentContracts := make(map[common.Address]*ExtensionContract) + if _, err := os.Stat(handler.saveFile); err == nil || !os.IsNotExist(err) { + blob, err := ioutil.ReadFile(handler.saveFile) + if err != nil { + return nil, err + } + + if err = json.Unmarshal(blob, ¤tContracts); err != nil { + return nil, err + } + } + return currentContracts, nil +} + +func (handler *JsonFileDataHandler) Save(extensionContracts map[common.Address]*ExtensionContract) error { + //no unmarshallable types, so can't error + output, _ := json.Marshal(&extensionContracts) + + if errSaving := ioutil.WriteFile(handler.saveFile, output, 0644); errSaving != nil { + log.Error("Couldn't save outstanding extension contract details") + return errSaving + } + return nil +} diff --git a/extension/data_handler_test.go b/extension/data_handler_test.go new file mode 100644 index 0000000000..82ed7bd6f3 --- /dev/null +++ b/extension/data_handler_test.go @@ -0,0 +1,44 @@ +package extension + +import ( + "encoding/json" + "io/ioutil" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestWriteContentsToFileWritesOkay(t *testing.T) { + extensionContracts := make(map[common.Address]*ExtensionContract) + extensionContracts[common.HexToAddress("0x2222222222222222222222222222222222222222")] = &ExtensionContract{ + ContractExtended: common.HexToAddress("0x1111111111111111111111111111111111111111"), + Initiator: common.HexToAddress("0x3333333333333333333333333333333333333333"), + Recipient: common.HexToAddress("0x4444444444444444444444444444444444444444"), + RecipientPtmKey: "1234567891234567891234567891234567891234567=", + ManagementContractAddress: common.HexToAddress("0x2222222222222222222222222222222222222222"), + CreationData: []byte("Sample Transaction Data"), + } + + datadir, err := ioutil.TempDir("", t.Name()) + if err != nil { + t.Errorf("could not create temp directory for test, error: %s", err.Error()) + } + + dataHandler := NewJsonFileDataHandler(datadir) + + if err := dataHandler.Save(extensionContracts); err != nil { + t.Errorf("error writing data to file, error: %s", err.Error()) + } + + loadedData, err := dataHandler.Load() + if err != nil { + t.Errorf("error reading data from file, error: %s", err.Error()) + } + + if !assert.ObjectsAreEqual(extensionContracts, loadedData) { + expected, _ := json.Marshal(extensionContracts) + actual, _ := json.Marshal(loadedData) + t.Errorf("expected data from file different to data written, expected %v, got %v", string(expected), string(actual)) + } +} diff --git a/extension/extensionContracts/contract_extender.go b/extension/extensionContracts/contract_extender.go new file mode 100644 index 0000000000..56e800b09c --- /dev/null +++ b/extension/extensionContracts/contract_extender.go @@ -0,0 +1,1559 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package extensionContracts + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ContractExtenderABI is the input ABI used to generate the binding from. +const ContractExtenderABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"creator\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"contractToExtend\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"checkIfExtensionFinished\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalNumberOfVoters\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"walletAddressesToVote\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"isFinished\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"nextuuid\",\"type\":\"string\"}],\"name\":\"setUuid\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"sharedDataHash\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"hash\",\"type\":\"string\"}],\"name\":\"setSharedStateHash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updatePartyMembers\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"voteOutcome\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"checkIfVoted\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"finish\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"votes\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"vote\",\"type\":\"bool\"},{\"name\":\"nextuuid\",\"type\":\"string\"}],\"name\":\"doVote\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"targetRecipientPTMKey\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"haveAllNodesVoted\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"contractAddress\",\"type\":\"address\"},{\"name\":\"recipientAddress\",\"type\":\"address\"},{\"name\":\"recipientPTMKey\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"recipientPTMKey\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"recipientAddress\",\"type\":\"address\"}],\"name\":\"NewContractExtensionContractCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"outcome\",\"type\":\"bool\"}],\"name\":\"AllNodesHaveAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CanPerformStateShare\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"ExtensionFinished\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"vote\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"NewVote\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tesserahash\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"uuid\",\"type\":\"string\"}],\"name\":\"StateShared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"uuid\",\"type\":\"string\"}],\"name\":\"UpdateMembers\",\"type\":\"event\"}]" + +var ContractExtenderParsedABI, _ = abi.JSON(strings.NewReader(ContractExtenderABI)) + +// ContractExtenderBin is the compiled bytecode used for deploying new contracts. +var ContractExtenderBin = "0x60806040523480156200001157600080fd5b506040516200135738038062001357833981018060405260608110156200003757600080fd5b81516020830151604084018051929491938201926401000000008111156200005e57600080fd5b820160208101848111156200007257600080fd5b81516401000000008111828201871017156200008d57600080fd5b5050600080546001600160a01b031916331790558051909350620000bb92506001915060208401906200028d565b50600280546001600160a01b038086166001600160a01b031992831617909255600380546001818101835560008381527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b92830180548616331790558354918201909355018054938616939092169290921790556040805160208101918290528290526200014d91600a91906200028d565b506009805460ff19166001179055600060068190555b600354811015620001c1576001600560006003848154811015156200018457fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff191691151591909117905560010162000163565b50600354600455604080516001600160a01b0380861682528416918101919091526060602080830182815284519284019290925283517f04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d9393879386938893608084019186019080838360005b83811015620002475781810151838201526020016200022d565b50505050905090810190601f168015620002755780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a150505062000332565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620002d057805160ff191683800117855562000300565b8280016001018555821562000300579182015b8281111562000300578251825591602001919060010190620002e3565b506200030e92915062000312565b5090565b6200032f91905b808211156200030e576000815560010162000319565b90565b61101580620003426000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c8063893971ba116100a2578063d56b288911610071578063d56b28891461037a578063d8bff5a514610382578063de5828cb146103a8578063e5af0f3014610457578063f57077d81461045f5761010b565b8063893971ba146102bc578063ac8b920514610362578063b5da45bb1461036a578063cb2805ec146103725761010b565b806379d41b8f116100de57806379d41b8f146101725780637b3529621461018f578063821e93da1461019757806388f520a01461023f5761010b565b806302d05d3f1461011057806315e56a6a146101345780631962cb9b1461013c5780633852772714610158575b600080fd5b610118610467565b604080516001600160a01b039092168252519081900360200190f35b610118610476565b610144610485565b604080519115158252519081900360200190f35b61016061048f565b60408051918252519081900360200190f35b6101186004803603602081101561018857600080fd5b5035610495565b6101446104bd565b61023d600480360360208110156101ad57600080fd5b8101906020810181356401000000008111156101c857600080fd5b8201836020820111156101da57600080fd5b803590602001918460018302840111640100000000831117156101fc57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506104c6945050505050565b005b610247610554565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610281578181015183820152602001610269565b50505050905090810190601f1680156102ae5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61023d600480360360208110156102d257600080fd5b8101906020810181356401000000008111156102ed57600080fd5b8201836020820111156102ff57600080fd5b8035906020019184600183028401116401000000008311171561032157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506105e2945050505050565b61023d61094c565b610144610a47565b610144610a50565b61023d610a66565b6101446004803603602081101561039857600080fd5b50356001600160a01b0316610b01565b61023d600480360360408110156103be57600080fd5b8135151591908101906040810160208201356401000000008111156103e257600080fd5b8201836020820111156103f457600080fd5b8035906020019184600183028401116401000000008311171561041657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610b16945050505050565b610247610bbb565b610144610c15565b6000546001600160a01b031681565b6002546001600160a01b031681565b600c5460ff165b90565b60045481565b60038054829081106104a357fe5b6000918252602090912001546001600160a01b0316905081565b600c5460ff1681565b600c5460ff161561050b57604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b600b805460018101808355600092909252825161054f917f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db901906020850190610ee1565b505050565b600a805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156105da5780601f106105af576101008083540402835291602001916105da565b820191906000526020600020905b8154815290600101906020018083116105bd57829003601f168201915b505050505081565b6000546001600160a01b0316331461062e57604051600160e51b62461bcd028152600401808060200182810382526023815260200180610fa26023913960400191505060405180910390fd5b600c5460ff161561067357604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b600a8054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156106ff5780601f106106d4576101008083540402835291602001916106ff565b820191906000526020600020905b8154815290600101906020018083116106e257829003601f168201915b505085519394508593151592506107639150505760408051600160e51b62461bcd02815260206004820152601860248201527f6e657720686173682063616e6e6f7420626520656d7074790000000000000000604482015290519081900360640190fd5b8151156107ba5760408051600160e51b62461bcd02815260206004820152601660248201527f7374617465206861736820616c72656164792073657400000000000000000000604482015290519081900360640190fd5b82516107cd90600a906020860190610ee1565b5060005b600b5481101561094357600254600b80547f67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e926001600160a01b031691600a918590811061081b57fe5b6000918252602091829020604080516001600160a01b038716815260609481018581528654600260001961010060018416150201909116049582018690529290930193908301906080840190869080156108b65780601f1061088b576101008083540402835291602001916108b6565b820191906000526020600020905b81548152906001019060200180831161089957829003601f168201915b505083810382528454600260001961010060018416150201909116048082526020909101908590801561092a5780601f106108ff5761010080835404028352916020019161092a565b820191906000526020600020905b81548152906001019060200180831161090d57829003601f168201915b50509550505050505060405180910390a16001016107d1565b5061054f610a66565b60005b600b54811015610a4457600254600b80547f8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44926001600160a01b031691908490811061099757fe5b6000918252602091829020604080516001600160a01b0386168152938401818152919092018054600260001961010060018416150201909116049284018390529291606083019084908015610a2d5780601f10610a0257610100808354040283529160200191610a2d565b820191906000526020600020905b815481529060010190602001808311610a1057829003601f168201915b5050935050505060405180910390a160010161094f565b50565b60095460ff1681565b3360009081526007602052604090205460ff1690565b600c5460ff1615610aab57604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b6000546001600160a01b03163314610af757604051600160e51b62461bcd028152600401808060200182810382526023815260200180610fa26023913960400191505060405180910390fd5b610aff610c1f565b565b60086020526000908152604090205460ff1681565b600c5460ff1615610b5b57604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b610b6482610c57565b8115610b7357610b73816104c6565b610b7b610e28565b60408051831515815233602082015281517f225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f929181900390910190a15050565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156105da5780601f106105af576101008083540402835291602001916105da565b6006546003541490565b600c805460ff191660011790556040517f79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede990600090a1565b600c5460ff1615610c9c57604051600160e51b62461bcd028152600401808060200182810382526028815260200180610f7a6028913960400191505060405180910390fd5b3360009081526005602052604090205460ff161515610d055760408051600160e51b62461bcd02815260206004820152601360248201527f6e6f7420616c6c6f77656420746f20766f746500000000000000000000000000604482015290519081900360640190fd5b3360009081526007602052604090205460ff1615610d6d5760408051600160e51b62461bcd02815260206004820152600d60248201527f616c726561647920766f74656400000000000000000000000000000000000000604482015290519081900360640190fd5b60095460ff161515610dc95760408051600160e51b62461bcd02815260206004820152601760248201527f766f74696e6720616c7265616479206465636c696e6564000000000000000000604482015290519081900360640190fd5b3360009081526007602090815260408083208054600160ff19918216811790925560089093529220805490911683151517905560068054909101905560095460ff168015610e145750805b6009805460ff191691151591909117905550565b60095460ff161515610e7557604080516000815290517ff20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd369181900360200190a1610e70610c1f565b610aff565b610e7d610c15565b15610aff57604080516001815290517ff20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd369181900360200190a16040517ffd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d2890600090a1565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610f2257805160ff1916838001178555610f4f565b82800160010185558215610f4f579182015b82811115610f4f578251825591602001919060010190610f34565b50610f5b929150610f5f565b5090565b61048c91905b80821115610f5b5760008155600101610f6556fe657874656e73696f6e2070726f6365737320636f6d706c657465642e2063616e6e6f7420766f74656f6e6c79206c6561646572206d617920706572666f726d207468697320616374696f6e657874656e73696f6e20686173206265656e206d61726b65642061732066696e6973686564a165627a7a723058201430571f2ddc1fc4db18fbe992d5cb9955a993fb01a34d163aebb1b5d891a6f00029" + +// DeployContractExtender deploys a new Ethereum contract, binding an instance of ContractExtender to it. +func DeployContractExtender(auth *bind.TransactOpts, backend bind.ContractBackend, contractAddress common.Address, recipientAddress common.Address, recipientPTMKey string) (common.Address, *types.Transaction, *ContractExtender, error) { + parsed, err := abi.JSON(strings.NewReader(ContractExtenderABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ContractExtenderBin), backend, contractAddress, recipientAddress, recipientPTMKey) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ContractExtender{ContractExtenderCaller: ContractExtenderCaller{contract: contract}, ContractExtenderTransactor: ContractExtenderTransactor{contract: contract}, ContractExtenderFilterer: ContractExtenderFilterer{contract: contract}}, nil +} + +// ContractExtender is an auto generated Go binding around an Ethereum contract. +type ContractExtender struct { + ContractExtenderCaller // Read-only binding to the contract + ContractExtenderTransactor // Write-only binding to the contract + ContractExtenderFilterer // Log filterer for contract events +} + +// ContractExtenderCaller is an auto generated read-only Go binding around an Ethereum contract. +type ContractExtenderCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractExtenderTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ContractExtenderTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractExtenderFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ContractExtenderFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractExtenderSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ContractExtenderSession struct { + Contract *ContractExtender // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ContractExtenderCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ContractExtenderCallerSession struct { + Contract *ContractExtenderCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ContractExtenderTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ContractExtenderTransactorSession struct { + Contract *ContractExtenderTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ContractExtenderRaw is an auto generated low-level Go binding around an Ethereum contract. +type ContractExtenderRaw struct { + Contract *ContractExtender // Generic contract binding to access the raw methods on +} + +// ContractExtenderCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ContractExtenderCallerRaw struct { + Contract *ContractExtenderCaller // Generic read-only contract binding to access the raw methods on +} + +// ContractExtenderTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ContractExtenderTransactorRaw struct { + Contract *ContractExtenderTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewContractExtender creates a new instance of ContractExtender, bound to a specific deployed contract. +func NewContractExtender(address common.Address, backend bind.ContractBackend) (*ContractExtender, error) { + contract, err := bindContractExtender(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ContractExtender{ContractExtenderCaller: ContractExtenderCaller{contract: contract}, ContractExtenderTransactor: ContractExtenderTransactor{contract: contract}, ContractExtenderFilterer: ContractExtenderFilterer{contract: contract}}, nil +} + +// NewContractExtenderCaller creates a new read-only instance of ContractExtender, bound to a specific deployed contract. +func NewContractExtenderCaller(address common.Address, caller bind.ContractCaller) (*ContractExtenderCaller, error) { + contract, err := bindContractExtender(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ContractExtenderCaller{contract: contract}, nil +} + +// NewContractExtenderTransactor creates a new write-only instance of ContractExtender, bound to a specific deployed contract. +func NewContractExtenderTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractExtenderTransactor, error) { + contract, err := bindContractExtender(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ContractExtenderTransactor{contract: contract}, nil +} + +// NewContractExtenderFilterer creates a new log filterer instance of ContractExtender, bound to a specific deployed contract. +func NewContractExtenderFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractExtenderFilterer, error) { + contract, err := bindContractExtender(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ContractExtenderFilterer{contract: contract}, nil +} + +// bindContractExtender binds a generic wrapper to an already deployed contract. +func bindContractExtender(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ContractExtenderABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ContractExtender *ContractExtenderRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ContractExtender.Contract.ContractExtenderCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ContractExtender *ContractExtenderRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractExtender.Contract.ContractExtenderTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ContractExtender *ContractExtenderRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ContractExtender.Contract.ContractExtenderTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ContractExtender *ContractExtenderCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ContractExtender.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ContractExtender *ContractExtenderTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractExtender.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ContractExtender *ContractExtenderTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ContractExtender.Contract.contract.Transact(opts, method, params...) +} + +// CheckIfExtensionFinished is a free data retrieval call binding the contract method 0x1962cb9b. +// +// Solidity: function checkIfExtensionFinished() constant returns(bool) +func (_ContractExtender *ContractExtenderCaller) CheckIfExtensionFinished(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "checkIfExtensionFinished") + return *ret0, err +} + +// CheckIfExtensionFinished is a free data retrieval call binding the contract method 0x1962cb9b. +// +// Solidity: function checkIfExtensionFinished() constant returns(bool) +func (_ContractExtender *ContractExtenderSession) CheckIfExtensionFinished() (bool, error) { + return _ContractExtender.Contract.CheckIfExtensionFinished(&_ContractExtender.CallOpts) +} + +// CheckIfExtensionFinished is a free data retrieval call binding the contract method 0x1962cb9b. +// +// Solidity: function checkIfExtensionFinished() constant returns(bool) +func (_ContractExtender *ContractExtenderCallerSession) CheckIfExtensionFinished() (bool, error) { + return _ContractExtender.Contract.CheckIfExtensionFinished(&_ContractExtender.CallOpts) +} + +// CheckIfVoted is a free data retrieval call binding the contract method 0xcb2805ec. +// +// Solidity: function checkIfVoted() constant returns(bool) +func (_ContractExtender *ContractExtenderCaller) CheckIfVoted(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "checkIfVoted") + return *ret0, err +} + +// CheckIfVoted is a free data retrieval call binding the contract method 0xcb2805ec. +// +// Solidity: function checkIfVoted() constant returns(bool) +func (_ContractExtender *ContractExtenderSession) CheckIfVoted() (bool, error) { + return _ContractExtender.Contract.CheckIfVoted(&_ContractExtender.CallOpts) +} + +// CheckIfVoted is a free data retrieval call binding the contract method 0xcb2805ec. +// +// Solidity: function checkIfVoted() constant returns(bool) +func (_ContractExtender *ContractExtenderCallerSession) CheckIfVoted() (bool, error) { + return _ContractExtender.Contract.CheckIfVoted(&_ContractExtender.CallOpts) +} + +// ContractToExtend is a free data retrieval call binding the contract method 0x15e56a6a. +// +// Solidity: function contractToExtend() constant returns(address) +func (_ContractExtender *ContractExtenderCaller) ContractToExtend(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "contractToExtend") + return *ret0, err +} + +// ContractToExtend is a free data retrieval call binding the contract method 0x15e56a6a. +// +// Solidity: function contractToExtend() constant returns(address) +func (_ContractExtender *ContractExtenderSession) ContractToExtend() (common.Address, error) { + return _ContractExtender.Contract.ContractToExtend(&_ContractExtender.CallOpts) +} + +// ContractToExtend is a free data retrieval call binding the contract method 0x15e56a6a. +// +// Solidity: function contractToExtend() constant returns(address) +func (_ContractExtender *ContractExtenderCallerSession) ContractToExtend() (common.Address, error) { + return _ContractExtender.Contract.ContractToExtend(&_ContractExtender.CallOpts) +} + +// Creator is a free data retrieval call binding the contract method 0x02d05d3f. +// +// Solidity: function creator() constant returns(address) +func (_ContractExtender *ContractExtenderCaller) Creator(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "creator") + return *ret0, err +} + +// Creator is a free data retrieval call binding the contract method 0x02d05d3f. +// +// Solidity: function creator() constant returns(address) +func (_ContractExtender *ContractExtenderSession) Creator() (common.Address, error) { + return _ContractExtender.Contract.Creator(&_ContractExtender.CallOpts) +} + +// Creator is a free data retrieval call binding the contract method 0x02d05d3f. +// +// Solidity: function creator() constant returns(address) +func (_ContractExtender *ContractExtenderCallerSession) Creator() (common.Address, error) { + return _ContractExtender.Contract.Creator(&_ContractExtender.CallOpts) +} + +// HaveAllNodesVoted is a free data retrieval call binding the contract method 0xf57077d8. +// +// Solidity: function haveAllNodesVoted() constant returns(bool) +func (_ContractExtender *ContractExtenderCaller) HaveAllNodesVoted(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "haveAllNodesVoted") + return *ret0, err +} + +// HaveAllNodesVoted is a free data retrieval call binding the contract method 0xf57077d8. +// +// Solidity: function haveAllNodesVoted() constant returns(bool) +func (_ContractExtender *ContractExtenderSession) HaveAllNodesVoted() (bool, error) { + return _ContractExtender.Contract.HaveAllNodesVoted(&_ContractExtender.CallOpts) +} + +// HaveAllNodesVoted is a free data retrieval call binding the contract method 0xf57077d8. +// +// Solidity: function haveAllNodesVoted() constant returns(bool) +func (_ContractExtender *ContractExtenderCallerSession) HaveAllNodesVoted() (bool, error) { + return _ContractExtender.Contract.HaveAllNodesVoted(&_ContractExtender.CallOpts) +} + +// IsFinished is a free data retrieval call binding the contract method 0x7b352962. +// +// Solidity: function isFinished() constant returns(bool) +func (_ContractExtender *ContractExtenderCaller) IsFinished(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "isFinished") + return *ret0, err +} + +// IsFinished is a free data retrieval call binding the contract method 0x7b352962. +// +// Solidity: function isFinished() constant returns(bool) +func (_ContractExtender *ContractExtenderSession) IsFinished() (bool, error) { + return _ContractExtender.Contract.IsFinished(&_ContractExtender.CallOpts) +} + +// IsFinished is a free data retrieval call binding the contract method 0x7b352962. +// +// Solidity: function isFinished() constant returns(bool) +func (_ContractExtender *ContractExtenderCallerSession) IsFinished() (bool, error) { + return _ContractExtender.Contract.IsFinished(&_ContractExtender.CallOpts) +} + +// SharedDataHash is a free data retrieval call binding the contract method 0x88f520a0. +// +// Solidity: function sharedDataHash() constant returns(string) +func (_ContractExtender *ContractExtenderCaller) SharedDataHash(opts *bind.CallOpts) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "sharedDataHash") + return *ret0, err +} + +// SharedDataHash is a free data retrieval call binding the contract method 0x88f520a0. +// +// Solidity: function sharedDataHash() constant returns(string) +func (_ContractExtender *ContractExtenderSession) SharedDataHash() (string, error) { + return _ContractExtender.Contract.SharedDataHash(&_ContractExtender.CallOpts) +} + +// SharedDataHash is a free data retrieval call binding the contract method 0x88f520a0. +// +// Solidity: function sharedDataHash() constant returns(string) +func (_ContractExtender *ContractExtenderCallerSession) SharedDataHash() (string, error) { + return _ContractExtender.Contract.SharedDataHash(&_ContractExtender.CallOpts) +} + +// TargetRecipientPTMKey is a free data retrieval call binding the contract method 0xe5af0f30. +// +// Solidity: function targetRecipientPTMKey() constant returns(string) +func (_ContractExtender *ContractExtenderCaller) TargetRecipientPTMKey(opts *bind.CallOpts) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "targetRecipientPTMKey") + return *ret0, err +} + +// TargetRecipientPTMKey is a free data retrieval call binding the contract method 0xe5af0f30. +// +// Solidity: function targetRecipientPTMKey() constant returns(string) +func (_ContractExtender *ContractExtenderSession) TargetRecipientPTMKey() (string, error) { + return _ContractExtender.Contract.TargetRecipientPTMKey(&_ContractExtender.CallOpts) +} + +// TargetRecipientPTMKey is a free data retrieval call binding the contract method 0xe5af0f30. +// +// Solidity: function targetRecipientPTMKey() constant returns(string) +func (_ContractExtender *ContractExtenderCallerSession) TargetRecipientPTMKey() (string, error) { + return _ContractExtender.Contract.TargetRecipientPTMKey(&_ContractExtender.CallOpts) +} + +// TotalNumberOfVoters is a free data retrieval call binding the contract method 0x38527727. +// +// Solidity: function totalNumberOfVoters() constant returns(uint256) +func (_ContractExtender *ContractExtenderCaller) TotalNumberOfVoters(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "totalNumberOfVoters") + return *ret0, err +} + +// TotalNumberOfVoters is a free data retrieval call binding the contract method 0x38527727. +// +// Solidity: function totalNumberOfVoters() constant returns(uint256) +func (_ContractExtender *ContractExtenderSession) TotalNumberOfVoters() (*big.Int, error) { + return _ContractExtender.Contract.TotalNumberOfVoters(&_ContractExtender.CallOpts) +} + +// TotalNumberOfVoters is a free data retrieval call binding the contract method 0x38527727. +// +// Solidity: function totalNumberOfVoters() constant returns(uint256) +func (_ContractExtender *ContractExtenderCallerSession) TotalNumberOfVoters() (*big.Int, error) { + return _ContractExtender.Contract.TotalNumberOfVoters(&_ContractExtender.CallOpts) +} + +// VoteOutcome is a free data retrieval call binding the contract method 0xb5da45bb. +// +// Solidity: function voteOutcome() constant returns(bool) +func (_ContractExtender *ContractExtenderCaller) VoteOutcome(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "voteOutcome") + return *ret0, err +} + +// VoteOutcome is a free data retrieval call binding the contract method 0xb5da45bb. +// +// Solidity: function voteOutcome() constant returns(bool) +func (_ContractExtender *ContractExtenderSession) VoteOutcome() (bool, error) { + return _ContractExtender.Contract.VoteOutcome(&_ContractExtender.CallOpts) +} + +// VoteOutcome is a free data retrieval call binding the contract method 0xb5da45bb. +// +// Solidity: function voteOutcome() constant returns(bool) +func (_ContractExtender *ContractExtenderCallerSession) VoteOutcome() (bool, error) { + return _ContractExtender.Contract.VoteOutcome(&_ContractExtender.CallOpts) +} + +// Votes is a free data retrieval call binding the contract method 0xd8bff5a5. +// +// Solidity: function votes(address ) constant returns(bool) +func (_ContractExtender *ContractExtenderCaller) Votes(opts *bind.CallOpts, arg0 common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "votes", arg0) + return *ret0, err +} + +// Votes is a free data retrieval call binding the contract method 0xd8bff5a5. +// +// Solidity: function votes(address ) constant returns(bool) +func (_ContractExtender *ContractExtenderSession) Votes(arg0 common.Address) (bool, error) { + return _ContractExtender.Contract.Votes(&_ContractExtender.CallOpts, arg0) +} + +// Votes is a free data retrieval call binding the contract method 0xd8bff5a5. +// +// Solidity: function votes(address ) constant returns(bool) +func (_ContractExtender *ContractExtenderCallerSession) Votes(arg0 common.Address) (bool, error) { + return _ContractExtender.Contract.Votes(&_ContractExtender.CallOpts, arg0) +} + +// WalletAddressesToVote is a free data retrieval call binding the contract method 0x79d41b8f. +// +// Solidity: function walletAddressesToVote(uint256 ) constant returns(address) +func (_ContractExtender *ContractExtenderCaller) WalletAddressesToVote(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _ContractExtender.contract.Call(opts, out, "walletAddressesToVote", arg0) + return *ret0, err +} + +// WalletAddressesToVote is a free data retrieval call binding the contract method 0x79d41b8f. +// +// Solidity: function walletAddressesToVote(uint256 ) constant returns(address) +func (_ContractExtender *ContractExtenderSession) WalletAddressesToVote(arg0 *big.Int) (common.Address, error) { + return _ContractExtender.Contract.WalletAddressesToVote(&_ContractExtender.CallOpts, arg0) +} + +// WalletAddressesToVote is a free data retrieval call binding the contract method 0x79d41b8f. +// +// Solidity: function walletAddressesToVote(uint256 ) constant returns(address) +func (_ContractExtender *ContractExtenderCallerSession) WalletAddressesToVote(arg0 *big.Int) (common.Address, error) { + return _ContractExtender.Contract.WalletAddressesToVote(&_ContractExtender.CallOpts, arg0) +} + +// DoVote is a paid mutator transaction binding the contract method 0xde5828cb. +// +// Solidity: function doVote(bool vote, string nextuuid) returns() +func (_ContractExtender *ContractExtenderTransactor) DoVote(opts *bind.TransactOpts, vote bool, nextuuid string) (*types.Transaction, error) { + return _ContractExtender.contract.Transact(opts, "doVote", vote, nextuuid) +} + +// DoVote is a paid mutator transaction binding the contract method 0xde5828cb. +// +// Solidity: function doVote(bool vote, string nextuuid) returns() +func (_ContractExtender *ContractExtenderSession) DoVote(vote bool, nextuuid string) (*types.Transaction, error) { + return _ContractExtender.Contract.DoVote(&_ContractExtender.TransactOpts, vote, nextuuid) +} + +// DoVote is a paid mutator transaction binding the contract method 0xde5828cb. +// +// Solidity: function doVote(bool vote, string nextuuid) returns() +func (_ContractExtender *ContractExtenderTransactorSession) DoVote(vote bool, nextuuid string) (*types.Transaction, error) { + return _ContractExtender.Contract.DoVote(&_ContractExtender.TransactOpts, vote, nextuuid) +} + +// Finish is a paid mutator transaction binding the contract method 0xd56b2889. +// +// Solidity: function finish() returns() +func (_ContractExtender *ContractExtenderTransactor) Finish(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractExtender.contract.Transact(opts, "finish") +} + +// Finish is a paid mutator transaction binding the contract method 0xd56b2889. +// +// Solidity: function finish() returns() +func (_ContractExtender *ContractExtenderSession) Finish() (*types.Transaction, error) { + return _ContractExtender.Contract.Finish(&_ContractExtender.TransactOpts) +} + +// Finish is a paid mutator transaction binding the contract method 0xd56b2889. +// +// Solidity: function finish() returns() +func (_ContractExtender *ContractExtenderTransactorSession) Finish() (*types.Transaction, error) { + return _ContractExtender.Contract.Finish(&_ContractExtender.TransactOpts) +} + +// SetSharedStateHash is a paid mutator transaction binding the contract method 0x893971ba. +// +// Solidity: function setSharedStateHash(string hash) returns() +func (_ContractExtender *ContractExtenderTransactor) SetSharedStateHash(opts *bind.TransactOpts, hash string) (*types.Transaction, error) { + return _ContractExtender.contract.Transact(opts, "setSharedStateHash", hash) +} + +// SetSharedStateHash is a paid mutator transaction binding the contract method 0x893971ba. +// +// Solidity: function setSharedStateHash(string hash) returns() +func (_ContractExtender *ContractExtenderSession) SetSharedStateHash(hash string) (*types.Transaction, error) { + return _ContractExtender.Contract.SetSharedStateHash(&_ContractExtender.TransactOpts, hash) +} + +// SetSharedStateHash is a paid mutator transaction binding the contract method 0x893971ba. +// +// Solidity: function setSharedStateHash(string hash) returns() +func (_ContractExtender *ContractExtenderTransactorSession) SetSharedStateHash(hash string) (*types.Transaction, error) { + return _ContractExtender.Contract.SetSharedStateHash(&_ContractExtender.TransactOpts, hash) +} + +// SetUuid is a paid mutator transaction binding the contract method 0x821e93da. +// +// Solidity: function setUuid(string nextuuid) returns() +func (_ContractExtender *ContractExtenderTransactor) SetUuid(opts *bind.TransactOpts, nextuuid string) (*types.Transaction, error) { + return _ContractExtender.contract.Transact(opts, "setUuid", nextuuid) +} + +// SetUuid is a paid mutator transaction binding the contract method 0x821e93da. +// +// Solidity: function setUuid(string nextuuid) returns() +func (_ContractExtender *ContractExtenderSession) SetUuid(nextuuid string) (*types.Transaction, error) { + return _ContractExtender.Contract.SetUuid(&_ContractExtender.TransactOpts, nextuuid) +} + +// SetUuid is a paid mutator transaction binding the contract method 0x821e93da. +// +// Solidity: function setUuid(string nextuuid) returns() +func (_ContractExtender *ContractExtenderTransactorSession) SetUuid(nextuuid string) (*types.Transaction, error) { + return _ContractExtender.Contract.SetUuid(&_ContractExtender.TransactOpts, nextuuid) +} + +// UpdatePartyMembers is a paid mutator transaction binding the contract method 0xac8b9205. +// +// Solidity: function updatePartyMembers() returns() +func (_ContractExtender *ContractExtenderTransactor) UpdatePartyMembers(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractExtender.contract.Transact(opts, "updatePartyMembers") +} + +// UpdatePartyMembers is a paid mutator transaction binding the contract method 0xac8b9205. +// +// Solidity: function updatePartyMembers() returns() +func (_ContractExtender *ContractExtenderSession) UpdatePartyMembers() (*types.Transaction, error) { + return _ContractExtender.Contract.UpdatePartyMembers(&_ContractExtender.TransactOpts) +} + +// UpdatePartyMembers is a paid mutator transaction binding the contract method 0xac8b9205. +// +// Solidity: function updatePartyMembers() returns() +func (_ContractExtender *ContractExtenderTransactorSession) UpdatePartyMembers() (*types.Transaction, error) { + return _ContractExtender.Contract.UpdatePartyMembers(&_ContractExtender.TransactOpts) +} + +// ContractExtenderAllNodesHaveAcceptedIterator is returned from FilterAllNodesHaveAccepted and is used to iterate over the raw logs and unpacked data for AllNodesHaveAccepted events raised by the ContractExtender contract. +type ContractExtenderAllNodesHaveAcceptedIterator struct { + Event *ContractExtenderAllNodesHaveAccepted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderAllNodesHaveAcceptedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderAllNodesHaveAccepted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderAllNodesHaveAccepted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderAllNodesHaveAcceptedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderAllNodesHaveAcceptedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderAllNodesHaveAccepted represents a AllNodesHaveAccepted event raised by the ContractExtender contract. +type ContractExtenderAllNodesHaveAccepted struct { + Outcome bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAllNodesHaveAccepted is a free log retrieval operation binding the contract event 0xf20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd36. +// +// Solidity: event AllNodesHaveAccepted(bool outcome) +func (_ContractExtender *ContractExtenderFilterer) FilterAllNodesHaveAccepted(opts *bind.FilterOpts) (*ContractExtenderAllNodesHaveAcceptedIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "AllNodesHaveAccepted") + if err != nil { + return nil, err + } + return &ContractExtenderAllNodesHaveAcceptedIterator{contract: _ContractExtender.contract, event: "AllNodesHaveAccepted", logs: logs, sub: sub}, nil +} + +var AllNodesHaveAcceptedTopicHash = "0xf20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd36" + +// WatchAllNodesHaveAccepted is a free log subscription operation binding the contract event 0xf20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd36. +// +// Solidity: event AllNodesHaveAccepted(bool outcome) +func (_ContractExtender *ContractExtenderFilterer) WatchAllNodesHaveAccepted(opts *bind.WatchOpts, sink chan<- *ContractExtenderAllNodesHaveAccepted) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "AllNodesHaveAccepted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderAllNodesHaveAccepted) + if err := _ContractExtender.contract.UnpackLog(event, "AllNodesHaveAccepted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAllNodesHaveAccepted is a log parse operation binding the contract event 0xf20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd36. +// +// Solidity: event AllNodesHaveAccepted(bool outcome) +func (_ContractExtender *ContractExtenderFilterer) ParseAllNodesHaveAccepted(log types.Log) (*ContractExtenderAllNodesHaveAccepted, error) { + event := new(ContractExtenderAllNodesHaveAccepted) + if err := _ContractExtender.contract.UnpackLog(event, "AllNodesHaveAccepted", log); err != nil { + return nil, err + } + return event, nil +} + +// ContractExtenderCanPerformStateShareIterator is returned from FilterCanPerformStateShare and is used to iterate over the raw logs and unpacked data for CanPerformStateShare events raised by the ContractExtender contract. +type ContractExtenderCanPerformStateShareIterator struct { + Event *ContractExtenderCanPerformStateShare // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderCanPerformStateShareIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderCanPerformStateShare) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderCanPerformStateShare) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderCanPerformStateShareIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderCanPerformStateShareIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderCanPerformStateShare represents a CanPerformStateShare event raised by the ContractExtender contract. +type ContractExtenderCanPerformStateShare struct { + Raw types.Log // Blockchain specific contextual infos +} + +// FilterCanPerformStateShare is a free log retrieval operation binding the contract event 0xfd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d28. +// +// Solidity: event CanPerformStateShare() +func (_ContractExtender *ContractExtenderFilterer) FilterCanPerformStateShare(opts *bind.FilterOpts) (*ContractExtenderCanPerformStateShareIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "CanPerformStateShare") + if err != nil { + return nil, err + } + return &ContractExtenderCanPerformStateShareIterator{contract: _ContractExtender.contract, event: "CanPerformStateShare", logs: logs, sub: sub}, nil +} + +var CanPerformStateShareTopicHash = "0xfd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d28" + +// WatchCanPerformStateShare is a free log subscription operation binding the contract event 0xfd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d28. +// +// Solidity: event CanPerformStateShare() +func (_ContractExtender *ContractExtenderFilterer) WatchCanPerformStateShare(opts *bind.WatchOpts, sink chan<- *ContractExtenderCanPerformStateShare) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "CanPerformStateShare") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderCanPerformStateShare) + if err := _ContractExtender.contract.UnpackLog(event, "CanPerformStateShare", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseCanPerformStateShare is a log parse operation binding the contract event 0xfd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d28. +// +// Solidity: event CanPerformStateShare() +func (_ContractExtender *ContractExtenderFilterer) ParseCanPerformStateShare(log types.Log) (*ContractExtenderCanPerformStateShare, error) { + event := new(ContractExtenderCanPerformStateShare) + if err := _ContractExtender.contract.UnpackLog(event, "CanPerformStateShare", log); err != nil { + return nil, err + } + return event, nil +} + +// ContractExtenderExtensionFinishedIterator is returned from FilterExtensionFinished and is used to iterate over the raw logs and unpacked data for ExtensionFinished events raised by the ContractExtender contract. +type ContractExtenderExtensionFinishedIterator struct { + Event *ContractExtenderExtensionFinished // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderExtensionFinishedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderExtensionFinished) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderExtensionFinished) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderExtensionFinishedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderExtensionFinishedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderExtensionFinished represents a ExtensionFinished event raised by the ContractExtender contract. +type ContractExtenderExtensionFinished struct { + Raw types.Log // Blockchain specific contextual infos +} + +// FilterExtensionFinished is a free log retrieval operation binding the contract event 0x79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede9. +// +// Solidity: event ExtensionFinished() +func (_ContractExtender *ContractExtenderFilterer) FilterExtensionFinished(opts *bind.FilterOpts) (*ContractExtenderExtensionFinishedIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "ExtensionFinished") + if err != nil { + return nil, err + } + return &ContractExtenderExtensionFinishedIterator{contract: _ContractExtender.contract, event: "ExtensionFinished", logs: logs, sub: sub}, nil +} + +var ExtensionFinishedTopicHash = "0x79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede9" + +// WatchExtensionFinished is a free log subscription operation binding the contract event 0x79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede9. +// +// Solidity: event ExtensionFinished() +func (_ContractExtender *ContractExtenderFilterer) WatchExtensionFinished(opts *bind.WatchOpts, sink chan<- *ContractExtenderExtensionFinished) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "ExtensionFinished") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderExtensionFinished) + if err := _ContractExtender.contract.UnpackLog(event, "ExtensionFinished", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseExtensionFinished is a log parse operation binding the contract event 0x79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede9. +// +// Solidity: event ExtensionFinished() +func (_ContractExtender *ContractExtenderFilterer) ParseExtensionFinished(log types.Log) (*ContractExtenderExtensionFinished, error) { + event := new(ContractExtenderExtensionFinished) + if err := _ContractExtender.contract.UnpackLog(event, "ExtensionFinished", log); err != nil { + return nil, err + } + return event, nil +} + +// ContractExtenderNewContractExtensionContractCreatedIterator is returned from FilterNewContractExtensionContractCreated and is used to iterate over the raw logs and unpacked data for NewContractExtensionContractCreated events raised by the ContractExtender contract. +type ContractExtenderNewContractExtensionContractCreatedIterator struct { + Event *ContractExtenderNewContractExtensionContractCreated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderNewContractExtensionContractCreatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderNewContractExtensionContractCreated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderNewContractExtensionContractCreated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderNewContractExtensionContractCreatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderNewContractExtensionContractCreatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderNewContractExtensionContractCreated represents a NewContractExtensionContractCreated event raised by the ContractExtender contract. +type ContractExtenderNewContractExtensionContractCreated struct { + ToExtend common.Address + RecipientPTMKey string + RecipientAddress common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNewContractExtensionContractCreated is a free log retrieval operation binding the contract event 0x04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d93. +// +// Solidity: event NewContractExtensionContractCreated(address toExtend, string recipientPTMKey, address recipientAddress) +func (_ContractExtender *ContractExtenderFilterer) FilterNewContractExtensionContractCreated(opts *bind.FilterOpts) (*ContractExtenderNewContractExtensionContractCreatedIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "NewContractExtensionContractCreated") + if err != nil { + return nil, err + } + return &ContractExtenderNewContractExtensionContractCreatedIterator{contract: _ContractExtender.contract, event: "NewContractExtensionContractCreated", logs: logs, sub: sub}, nil +} + +var NewContractExtensionContractCreatedTopicHash = "0x04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d93" + +// WatchNewContractExtensionContractCreated is a free log subscription operation binding the contract event 0x04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d93. +// +// Solidity: event NewContractExtensionContractCreated(address toExtend, string recipientPTMKey, address recipientAddress) +func (_ContractExtender *ContractExtenderFilterer) WatchNewContractExtensionContractCreated(opts *bind.WatchOpts, sink chan<- *ContractExtenderNewContractExtensionContractCreated) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "NewContractExtensionContractCreated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderNewContractExtensionContractCreated) + if err := _ContractExtender.contract.UnpackLog(event, "NewContractExtensionContractCreated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNewContractExtensionContractCreated is a log parse operation binding the contract event 0x04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d93. +// +// Solidity: event NewContractExtensionContractCreated(address toExtend, string recipientPTMKey, address recipientAddress) +func (_ContractExtender *ContractExtenderFilterer) ParseNewContractExtensionContractCreated(log types.Log) (*ContractExtenderNewContractExtensionContractCreated, error) { + event := new(ContractExtenderNewContractExtensionContractCreated) + if err := _ContractExtender.contract.UnpackLog(event, "NewContractExtensionContractCreated", log); err != nil { + return nil, err + } + return event, nil +} + +// ContractExtenderNewVoteIterator is returned from FilterNewVote and is used to iterate over the raw logs and unpacked data for NewVote events raised by the ContractExtender contract. +type ContractExtenderNewVoteIterator struct { + Event *ContractExtenderNewVote // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderNewVoteIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderNewVote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderNewVote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderNewVoteIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderNewVoteIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderNewVote represents a NewVote event raised by the ContractExtender contract. +type ContractExtenderNewVote struct { + Vote bool + Voter common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNewVote is a free log retrieval operation binding the contract event 0x225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f. +// +// Solidity: event NewVote(bool vote, address voter) +func (_ContractExtender *ContractExtenderFilterer) FilterNewVote(opts *bind.FilterOpts) (*ContractExtenderNewVoteIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "NewVote") + if err != nil { + return nil, err + } + return &ContractExtenderNewVoteIterator{contract: _ContractExtender.contract, event: "NewVote", logs: logs, sub: sub}, nil +} + +var NewVoteTopicHash = "0x225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f" + +// WatchNewVote is a free log subscription operation binding the contract event 0x225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f. +// +// Solidity: event NewVote(bool vote, address voter) +func (_ContractExtender *ContractExtenderFilterer) WatchNewVote(opts *bind.WatchOpts, sink chan<- *ContractExtenderNewVote) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "NewVote") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderNewVote) + if err := _ContractExtender.contract.UnpackLog(event, "NewVote", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNewVote is a log parse operation binding the contract event 0x225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f. +// +// Solidity: event NewVote(bool vote, address voter) +func (_ContractExtender *ContractExtenderFilterer) ParseNewVote(log types.Log) (*ContractExtenderNewVote, error) { + event := new(ContractExtenderNewVote) + if err := _ContractExtender.contract.UnpackLog(event, "NewVote", log); err != nil { + return nil, err + } + return event, nil +} + +// ContractExtenderStateSharedIterator is returned from FilterStateShared and is used to iterate over the raw logs and unpacked data for StateShared events raised by the ContractExtender contract. +type ContractExtenderStateSharedIterator struct { + Event *ContractExtenderStateShared // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderStateSharedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderStateShared) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderStateShared) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderStateSharedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderStateSharedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderStateShared represents a StateShared event raised by the ContractExtender contract. +type ContractExtenderStateShared struct { + ToExtend common.Address + Tesserahash string + Uuid string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterStateShared is a free log retrieval operation binding the contract event 0x67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e. +// +// Solidity: event StateShared(address toExtend, string tesserahash, string uuid) +func (_ContractExtender *ContractExtenderFilterer) FilterStateShared(opts *bind.FilterOpts) (*ContractExtenderStateSharedIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "StateShared") + if err != nil { + return nil, err + } + return &ContractExtenderStateSharedIterator{contract: _ContractExtender.contract, event: "StateShared", logs: logs, sub: sub}, nil +} + +var StateSharedTopicHash = "0x67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e" + +// WatchStateShared is a free log subscription operation binding the contract event 0x67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e. +// +// Solidity: event StateShared(address toExtend, string tesserahash, string uuid) +func (_ContractExtender *ContractExtenderFilterer) WatchStateShared(opts *bind.WatchOpts, sink chan<- *ContractExtenderStateShared) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "StateShared") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderStateShared) + if err := _ContractExtender.contract.UnpackLog(event, "StateShared", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseStateShared is a log parse operation binding the contract event 0x67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e. +// +// Solidity: event StateShared(address toExtend, string tesserahash, string uuid) +func (_ContractExtender *ContractExtenderFilterer) ParseStateShared(log types.Log) (*ContractExtenderStateShared, error) { + event := new(ContractExtenderStateShared) + if err := _ContractExtender.contract.UnpackLog(event, "StateShared", log); err != nil { + return nil, err + } + return event, nil +} + +// ContractExtenderUpdateMembersIterator is returned from FilterUpdateMembers and is used to iterate over the raw logs and unpacked data for UpdateMembers events raised by the ContractExtender contract. +type ContractExtenderUpdateMembersIterator struct { + Event *ContractExtenderUpdateMembers // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractExtenderUpdateMembersIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractExtenderUpdateMembers) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractExtenderUpdateMembers) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractExtenderUpdateMembersIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractExtenderUpdateMembersIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractExtenderUpdateMembers represents a UpdateMembers event raised by the ContractExtender contract. +type ContractExtenderUpdateMembers struct { + ToExtend common.Address + Uuid string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUpdateMembers is a free log retrieval operation binding the contract event 0x8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44. +// +// Solidity: event UpdateMembers(address toExtend, string uuid) +func (_ContractExtender *ContractExtenderFilterer) FilterUpdateMembers(opts *bind.FilterOpts) (*ContractExtenderUpdateMembersIterator, error) { + + logs, sub, err := _ContractExtender.contract.FilterLogs(opts, "UpdateMembers") + if err != nil { + return nil, err + } + return &ContractExtenderUpdateMembersIterator{contract: _ContractExtender.contract, event: "UpdateMembers", logs: logs, sub: sub}, nil +} + +var UpdateMembersTopicHash = "0x8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44" + +// WatchUpdateMembers is a free log subscription operation binding the contract event 0x8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44. +// +// Solidity: event UpdateMembers(address toExtend, string uuid) +func (_ContractExtender *ContractExtenderFilterer) WatchUpdateMembers(opts *bind.WatchOpts, sink chan<- *ContractExtenderUpdateMembers) (event.Subscription, error) { + + logs, sub, err := _ContractExtender.contract.WatchLogs(opts, "UpdateMembers") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractExtenderUpdateMembers) + if err := _ContractExtender.contract.UnpackLog(event, "UpdateMembers", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseUpdateMembers is a log parse operation binding the contract event 0x8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44. +// +// Solidity: event UpdateMembers(address toExtend, string uuid) +func (_ContractExtender *ContractExtenderFilterer) ParseUpdateMembers(log types.Log) (*ContractExtenderUpdateMembers, error) { + event := new(ContractExtenderUpdateMembers) + if err := _ContractExtender.contract.UnpackLog(event, "UpdateMembers", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/extension/extensionContracts/contract_extender.sol b/extension/extensionContracts/contract_extender.sol new file mode 100644 index 0000000000..47f8ecbe9d --- /dev/null +++ b/extension/extensionContracts/contract_extender.sol @@ -0,0 +1,168 @@ +pragma solidity ^0.5.3; + +contract ContractExtender { + + //target details - what, who and when to extend + address public creator; + string public targetRecipientPTMKey; + address public contractToExtend; + + //list of wallet addresses that can cast votes + address[] public walletAddressesToVote; + uint256 public totalNumberOfVoters; + mapping(address => bool) walletAddressesToVoteMap; + uint256 numberOfVotesSoFar; + mapping(address => bool) hasVotedMapping; + mapping(address => bool) public votes; + + //contains the total outcome of voting + //true if ALL nodes vote true, false if ANY node votes false + bool public voteOutcome; + + //the hash of the shared payload + string public sharedDataHash; + string[] uuids; + + //if creator cancelled this extension + bool public isFinished; + + // General housekeeping + event NewContractExtensionContractCreated(address toExtend, string recipientPTMKey, address recipientAddress); //to tell nodes a new extension is happening + event AllNodesHaveAccepted(bool outcome); //when all nodes have voted + event CanPerformStateShare(); //when all nodes have voted & the recipient has accepted + event ExtensionFinished(); //if the extension is cancelled or completed + event NewVote(bool vote, address voter); // when someone voted (either true or false) + event StateShared(address toExtend, string tesserahash, string uuid); //when the state is shared and can be replayed into the database + event UpdateMembers(address toExtend, string uuid); //to update the original transaction hash for the new party member + + constructor(address contractAddress, address recipientAddress, string memory recipientPTMKey) public { + creator = msg.sender; + + targetRecipientPTMKey = recipientPTMKey; + + contractToExtend = contractAddress; + walletAddressesToVote.push(msg.sender); + walletAddressesToVote.push(recipientAddress); + + sharedDataHash = ""; + + voteOutcome = true; + numberOfVotesSoFar = 0; + + for (uint256 i = 0; i < walletAddressesToVote.length; i++) { + walletAddressesToVoteMap[walletAddressesToVote[i]] = true; + } + totalNumberOfVoters = walletAddressesToVote.length; + emit NewContractExtensionContractCreated(contractAddress, recipientPTMKey, recipientAddress); + } + + ///////////////////////////////////////////////////////////////////////////////////// + //modifiers + ///////////////////////////////////////////////////////////////////////////////////// + modifier notFinished() { + require(!isFinished, "extension has been marked as finished"); + _; + } + + modifier onlyCreator() { + require(msg.sender == creator, "only leader may perform this action"); + _; + } + + ///////////////////////////////////////////////////////////////////////////////////// + //main + ///////////////////////////////////////////////////////////////////////////////////// + function haveAllNodesVoted() public view returns (bool) { + return walletAddressesToVote.length == numberOfVotesSoFar; + } + + // returns true if the sender address has already voted on the + // extension contracts + function checkIfVoted() public view returns (bool) { + return hasVotedMapping[msg.sender]; + } + + // returns true if the contract extension is finished + function checkIfExtensionFinished() public view returns (bool) { + return isFinished; + } + + // single node vote to either extend or not + // can't have voted before + function doVote(bool vote, string memory nextuuid) public notFinished() { + cast(vote); + if (vote) { + setUuid(nextuuid); + } + // check if voting has finished + checkVotes(); + emit NewVote(vote, msg.sender); + } + + // this event is emitted to tell each node to use this tx as the original tx + // only if they voted for it + function updatePartyMembers() public { + for(uint256 i = 0; i < uuids.length; i++) { + emit UpdateMembers(contractToExtend, uuids[i]); + } + } + + //state has been shared off chain via a private transaction, the hash the PTM generated is set here + function setSharedStateHash(string memory hash) public onlyCreator() notFinished() { + bytes memory hashAsBytes = bytes(sharedDataHash); + bytes memory incomingAsBytes = bytes(hash); + + require(incomingAsBytes.length != 0, "new hash cannot be empty"); + require(hashAsBytes.length == 0, "state hash already set"); + sharedDataHash = hash; + + for(uint256 i = 0; i < uuids.length; i++) { + emit StateShared(contractToExtend, sharedDataHash, uuids[i]); + } + + finish(); + } + + //close the contract to further modifications + function finish() public notFinished() onlyCreator() { + setFinished(); + } + + //this sets a unique code that only the sending node has access to, that can be referred to later + function setUuid(string memory nextuuid) public notFinished() { + uuids.push(nextuuid); + } + + // Internal methods + function setFinished() internal { + isFinished = true; + emit ExtensionFinished(); + } + + // checks if all the conditions for voting have been met + // either all voted true and target accepted, or someone voted false + function checkVotes() internal { + if (!voteOutcome) { + emit AllNodesHaveAccepted(false); + setFinished(); + return; + } + + if (haveAllNodesVoted()) { + emit AllNodesHaveAccepted(true); + emit CanPerformStateShare(); + } + } + + function cast(bool vote) internal { + require(!isFinished, "extension process completed. cannot vote"); + require(walletAddressesToVoteMap[msg.sender], "not allowed to vote"); + require(!hasVotedMapping[msg.sender], "already voted"); + require(voteOutcome, "voting already declined"); + + hasVotedMapping[msg.sender] = true; + votes[msg.sender] = vote; + numberOfVotesSoFar++; + voteOutcome = voteOutcome && vote; + } +} \ No newline at end of file diff --git a/extension/extensionContracts/extensionHandler.go b/extension/extensionContracts/extensionHandler.go new file mode 100644 index 0000000000..39f43aaf8b --- /dev/null +++ b/extension/extensionContracts/extensionHandler.go @@ -0,0 +1,18 @@ +package extensionContracts + +import "github.com/ethereum/go-ethereum/common" + +func UnpackStateSharedLog(logData []byte) (common.Address, string, string, error) { + decodedLog := new(ContractExtenderStateShared) + if err := ContractExtenderParsedABI.Unpack(decodedLog, "StateShared", logData); err != nil { + return common.Address{}, "", "", err + } + return decodedLog.ToExtend, decodedLog.Tesserahash, decodedLog.Uuid, nil +} + +func UnpackNewExtensionCreatedLog(data []byte) (*ContractExtenderNewContractExtensionContractCreated, error) { + newExtensionEvent := new(ContractExtenderNewContractExtensionContractCreated) + err := ContractExtenderParsedABI.Unpack(newExtensionEvent, "NewContractExtensionContractCreated", data) + + return newExtensionEvent, err +} diff --git a/extension/extensionContracts/types.go b/extension/extensionContracts/types.go new file mode 100644 index 0000000000..60f75e7242 --- /dev/null +++ b/extension/extensionContracts/types.go @@ -0,0 +1,9 @@ +package extensionContracts + +import ( + "github.com/ethereum/go-ethereum/core/state" +) + +type AccountWithMetadata struct { + State state.DumpAccount `json:"state"` +} diff --git a/extension/extension_utilities.go b/extension/extension_utilities.go new file mode 100644 index 0000000000..ff0a89a37c --- /dev/null +++ b/extension/extension_utilities.go @@ -0,0 +1,38 @@ +package extension + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" +) + +// generateUuid sends some data to the linked Private Transaction Manager which +// uses a randomly generated key to encrypt the data and then hash it this +// means we get a effectively random hash, whilst also having a reference +// transaction inside the PTM +func generateUuid(contractAddress common.Address, privateFrom string, privateFor []string, ptm private.PrivateTransactionManager) (string, error) { + + // to ensure recoverability , the UUID generation logic is as below: + // 1. Call Tessera to encrypt the management contract address + // 2. Send the encrypted payload to all participants on the contract extension + // 3. Use the received hash as the UUID + payloadHash, err := ptm.EncryptPayload(contractAddress.Bytes(), privateFrom, []string{}, &engine.ExtraMetadata{}) + if err != nil { + return "", err + } + + _, _, hash, err := ptm.Send(payloadHash, privateFrom, privateFor, &engine.ExtraMetadata{}) + if err != nil { + return "", err + } + return hash.String(), nil +} + +func checkAddressInList(addressToFind common.Address, addressList []common.Address) bool { + for _, addr := range addressList { + if addressToFind == addr { + return true + } + } + return false +} diff --git a/extension/privacyExtension/state_set_utilities.go b/extension/privacyExtension/state_set_utilities.go new file mode 100644 index 0000000000..5b1814d191 --- /dev/null +++ b/extension/privacyExtension/state_set_utilities.go @@ -0,0 +1,97 @@ +package privacyExtension + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + extension "github.com/ethereum/go-ethereum/extension/extensionContracts" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" +) + +func setState(privateState *state.StateDB, accounts map[string]extension.AccountWithMetadata, privacyMetaData *state.PrivacyMetadata, managedParties []string) bool { + log.Debug("Extension: set private state explicitly from state dump") + for key, value := range accounts { + stateDump := value.State + + contractAddress := common.HexToAddress(key) + + newBalance, errBalanceSet := new(big.Int).SetString(stateDump.Balance, 10) + if !errBalanceSet { + log.Error("could not set address balance", "address", key, "balance", stateDump.Balance) + return false + } + privateState.SetBalance(contractAddress, newBalance) + privateState.SetNonce(contractAddress, stateDump.Nonce) + privateState.SetCode(contractAddress, common.Hex2Bytes(stateDump.Code)) + for keyStore, valueStore := range stateDump.Storage { + privateState.SetState(contractAddress, keyStore, common.HexToHash(valueStore)) + } + if privacyMetaData.PrivacyFlag != engine.PrivacyFlagStandardPrivate { + privateState.SetPrivacyMetadata(contractAddress, privacyMetaData) + } + if managedParties != nil { + privateState.SetManagedParties(contractAddress, managedParties) + } + } + return true +} + +// updates the privacy metadata +func setPrivacyMetadata(privateState *state.StateDB, address common.Address, hash string) { + privacyMetaData, err := privateState.GetPrivacyMetadata(address) + if err != nil || privacyMetaData.PrivacyFlag.IsStandardPrivate() { + return + } + + ptmHash, err := common.Base64ToEncryptedPayloadHash(hash) + if err != nil { + log.Error("setting privacy metadata failed", "err", err) + return + } + pm := state.NewStatePrivacyMetadata(ptmHash, privacyMetaData.PrivacyFlag) + privateState.SetPrivacyMetadata(address, pm) +} + +func setManagedParties(ptm private.PrivateTransactionManager, privateState *state.StateDB, address common.Address, hash string) { + existingManagedParties, err := privateState.GetManagedParties(address) + if err != nil { + return + } + + ptmHash, err := common.Base64ToEncryptedPayloadHash(hash) + if err != nil { + log.Error("setting privacy metadata failed", "err", err) + return + } + + _, managedParties, _, _, _ := ptm.Receive(ptmHash) + newManagedParties := common.AppendSkipDuplicates(existingManagedParties, managedParties...) + privateState.SetManagedParties(address, newManagedParties) +} + +func logContainsExtensionTopic(receivedLog *types.Log) bool { + if len(receivedLog.Topics) != 1 { + return false + } + return receivedLog.Topics[0].String() == extension.StateSharedTopicHash +} + +// validateAccountsExist checks that all the accounts in the expected list are +// present in the state map, and that no other accounts exist in the state map +// that are unexpected +func validateAccountsExist(expectedAccounts []common.Address, actualAccounts map[string]extension.AccountWithMetadata) bool { + if len(expectedAccounts) != len(actualAccounts) { + return false + } + for _, account := range expectedAccounts { + _, exists := actualAccounts[account.String()] + if !exists { + return false + } + } + return true +} diff --git a/extension/privacyExtension/state_set_utilities_test.go b/extension/privacyExtension/state_set_utilities_test.go new file mode 100644 index 0000000000..aa222b6f20 --- /dev/null +++ b/extension/privacyExtension/state_set_utilities_test.go @@ -0,0 +1,151 @@ +package privacyExtension + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + extension "github.com/ethereum/go-ethereum/extension/extensionContracts" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/stretchr/testify/assert" +) + +func TestLogContainsExtensionTopicWithWrongLengthReturnsFalse(t *testing.T) { + testLog := &types.Log{ + Topics: []common.Hash{{}, {}}, + } + + contained := logContainsExtensionTopic(testLog) + + if contained { + t.Errorf("expected value '%t', but got '%t'", false, contained) + } +} + +func TestLogContainsExtensionTopicWithWrongHashReturnsFalse(t *testing.T) { + testLog := &types.Log{ + Topics: []common.Hash{common.HexToHash("0xf20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd36")}, + } + + contained := logContainsExtensionTopic(testLog) + + if contained { + t.Errorf("expected value '%t', but got '%t'", false, contained) + } +} + +func TestLogContainsExtensionTopicWithCorrectHashReturnsTrue(t *testing.T) { + testLog := &types.Log{ + Topics: []common.Hash{common.HexToHash("0x67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e")}, + } + + contained := logContainsExtensionTopic(testLog) + + if !contained { + t.Errorf("expected value '%t', but got '%t'", true, contained) + } +} + +func createStateDb(t *testing.T) *state.StateDB { + input := `{"0x2222222222222222222222222222222222222222":{"state":{"balance":"22","nonce":5,"root":"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3","code":"03030303030303","storage":{}}}}` + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + + var accounts map[string]extension.AccountWithMetadata + if err := json.Unmarshal([]byte(input), &accounts); err != nil { + t.Errorf("error when unmarshalling static data: %s", err.Error()) + } + + success := setState(statedb, accounts, &state.PrivacyMetadata{}, nil) + if !success { + t.Errorf("unexpected error when setting state") + } + + return statedb +} + +func TestStateSetWithListedAccounts(t *testing.T) { + statedb := createStateDb(t) + + address := common.HexToAddress("0x2222222222222222222222222222222222222222") + balance := statedb.GetBalance(address) + code := statedb.GetCode(address) + nonce := statedb.GetNonce(address) + storage, _ := statedb.GetStorageRoot(address) + + if balance.Uint64() != 22 { + t.Errorf("expect Balance value of '%d', but got '%d'", 22, balance.Uint64()) + return + } + + expectedCode := []byte{3, 3, 3, 3, 3, 3, 3} + if !bytes.Equal(code, expectedCode) { + t.Errorf("expect Code value of '%d', but got '%d'", expectedCode, code) + return + } + + if nonce != 5 { + t.Errorf("expect Nonce value of '%d', but got '%d'", 5, nonce) + return + } + + expectedStorageHash := common.FromHex("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + if !bytes.Equal(storage.Bytes(), expectedStorageHash) { + t.Errorf("expect Storage value of '%d', but got '%s'", expectedStorageHash, storage) + return + } +} + +func TestStateSetWithListedAccountsFailsOnInvalidBalance(t *testing.T) { + input := `{"0x2222222222222222222222222222222222222222":{"state":{"balance":"invalid","nonce":5,"root":"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3","code":"03030303030303","storage":{}}}}` + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + + var accounts map[string]extension.AccountWithMetadata + if err := json.Unmarshal([]byte(input), &accounts); err != nil { + t.Errorf("error when unmarshalling static data: %s", err.Error()) + } + + success := setState(statedb, accounts, &state.PrivacyMetadata{}, nil) + if success { + t.Errorf("error expected when setting state") + } +} + +func Test_setPrivacyMetadata(t *testing.T) { + statedb := createStateDb(t) + address := common.HexToAddress("0x2222222222222222222222222222222222222222") + + // call setPrivacyMetaData + arbitraryBytes1 := []byte{10} + hash := common.BytesToEncryptedPayloadHash(arbitraryBytes1) + setPrivacyMetadata(statedb, address, base64.StdEncoding.EncodeToString(arbitraryBytes1)) + + // we don't save PrivacyMetadata if it's standardprivate + privacyMetaData, err := statedb.GetPrivacyMetadata(address) + assert.Error(t, err, common.ErrNoAccountExtraData) + + privacyMetaData = &state.PrivacyMetadata{CreationTxHash: hash, PrivacyFlag: engine.PrivacyFlagPartyProtection} + statedb.SetPrivacyMetadata(address, privacyMetaData) + + privacyMetaData, err = statedb.GetPrivacyMetadata(address) + if err != nil { + t.Errorf("expected error to be nil, got err %s", err) + } + assert.Equal(t, engine.PrivacyFlagPartyProtection, privacyMetaData.PrivacyFlag) + assert.Equal(t, hash, privacyMetaData.CreationTxHash) + + arbitraryBytes2 := []byte{20} + newHash := common.BytesToEncryptedPayloadHash(arbitraryBytes2) + setPrivacyMetadata(statedb, address, base64.StdEncoding.EncodeToString(arbitraryBytes2)) + + privacyMetaData, err = statedb.GetPrivacyMetadata(address) + if err != nil { + t.Errorf("expected error to be nil, got err %s", err) + } + assert.Equal(t, engine.PrivacyFlagPartyProtection, privacyMetaData.PrivacyFlag) + assert.Equal(t, newHash, privacyMetaData.CreationTxHash) +} diff --git a/extension/privacyExtension/state_setter.go b/extension/privacyExtension/state_setter.go new file mode 100644 index 0000000000..3f1b70dedc --- /dev/null +++ b/extension/privacyExtension/state_setter.go @@ -0,0 +1,157 @@ +package privacyExtension + +import ( + "bytes" + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + extension "github.com/ethereum/go-ethereum/extension/extensionContracts" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private" +) + +var DefaultExtensionHandler *ExtensionHandler + +type ExtensionHandler struct { + ptm private.PrivateTransactionManager + isMultitenant bool +} + +func Init() { + DefaultExtensionHandler = NewExtensionHandler(private.P) +} + +func NewExtensionHandler(transactionManager private.PrivateTransactionManager) *ExtensionHandler { + return &ExtensionHandler{ptm: transactionManager} +} + +func (handler *ExtensionHandler) SupportMultitenancy(b bool) { + handler.isMultitenant = b +} + +func (handler *ExtensionHandler) CheckExtensionAndSetPrivateState(txLogs []*types.Log, privateState *state.StateDB) { + extraMetaDataUpdated := false + for _, txLog := range txLogs { + if logContainsExtensionTopic(txLog) { + //this is a direct state share + address, hash, uuid, err := extension.UnpackStateSharedLog(txLog.Data) + if err != nil { + continue + } + + // check if state exists for the extension address. If yes then skip + // processing + if privateState.GetCode(address) != nil { + if extraMetaDataUpdated { + continue + } + // check the privacy flag of the contract. if its other than + // 0 then need to update the privacy metadata for the contract + //TODO: validate the old and new parties to ensure that all old parties are there + setPrivacyMetadata(privateState, address, hash) + if handler.isMultitenant { + setManagedParties(handler.ptm, privateState, address, hash) + } + extraMetaDataUpdated = true + } else { + managedParties, accounts, privacyMetaData, found := handler.FetchStateData(txLog.Address, hash, uuid) + if !found { + continue + } + if !handler.isMultitenant { + managedParties = nil + } + if !validateAccountsExist([]common.Address{address}, accounts) { + log.Error("Account mismatch", "expected", address, "found", accounts) + continue + } + snapshotId := privateState.Snapshot() + + if success := setState(privateState, accounts, privacyMetaData, managedParties); !success { + privateState.RevertToSnapshot(snapshotId) + } + } + + } + } +} + +func (handler *ExtensionHandler) FetchStateData(address common.Address, hash string, uuid string) ([]string, map[string]extension.AccountWithMetadata, *state.PrivacyMetadata, bool) { + if uuidIsSentByUs := handler.UuidIsOwn(address, uuid); !uuidIsSentByUs { + return nil, nil, nil, false + } + + managedParties, stateData, privacyMetaData, ok := handler.FetchDataFromPTM(hash) + if !ok { + //there is nothing to do here, the state wasn't shared with us + log.Error("Extension: No state shared with us") + return nil, nil, nil, false + } + + var accounts map[string]extension.AccountWithMetadata + if err := json.Unmarshal(stateData, &accounts); err != nil { + log.Error("Extension: Could not unmarshal data") + return nil, nil, nil, false + } + + return managedParties, accounts, privacyMetaData, true +} + +// Checks + +func (handler *ExtensionHandler) FetchDataFromPTM(hash string) ([]string, []byte, *state.PrivacyMetadata, bool) { + ptmHash, _ := common.Base64ToEncryptedPayloadHash(hash) + _, managedParties, stateData, extraMetaData, err := handler.ptm.Receive(ptmHash) + + if stateData == nil { + log.Error("No state data found in PTM", "ptm hash", hash) + return nil, nil, nil, false + } + if err != nil { + log.Error("Error receiving state data from PTM", "ptm hash", hash, "err", err.Error()) + return nil, nil, nil, false + } + + privacyMetaData := state.NewStatePrivacyMetadata(ptmHash, extraMetaData.PrivacyFlag) + return managedParties, stateData, privacyMetaData, true +} + +func (handler *ExtensionHandler) UuidIsOwn(address common.Address, uuid string) bool { + if uuid == "" { + //we never called accept + log.Warn("Extension: State shared by accept never called") + return false + } + encryptedTxHash := common.BytesToEncryptedPayloadHash(common.FromHex(uuid)) + isSender, err := handler.ptm.IsSender(encryptedTxHash) + if isSender { + if err != nil { + log.Debug("Extension: could not determine if we are sender", "err", err.Error()) + return false + } + + _, _, encryptedPayload, _, err := handler.ptm.Receive(encryptedTxHash) + if err != nil { + log.Debug("Extension: payload not found", "err", err) + return false + } + var payload common.DecryptRequest + err = json.Unmarshal(encryptedPayload, &payload) + if err != nil { + log.Debug("Extension: payload unmarshal failed", "err", err) + } + + contractDetails, _, err := handler.ptm.DecryptPayload(payload) + if err != nil { + log.Debug("Extension: payload decrypt failed", "err", err) + } + + if !bytes.Equal(contractDetails, address.Bytes()) { + log.Error("Extension: wrong address in retrieved UUID") + return false + } + } + return isSender +} diff --git a/extension/services_factory.go b/extension/services_factory.go new file mode 100644 index 0000000000..8514da25af --- /dev/null +++ b/extension/services_factory.go @@ -0,0 +1,65 @@ +package extension + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/extension/privacyExtension" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/private" +) + +type ServicesFactory interface { + BackendService() *PrivacyService + + AccountManager() *accounts.Manager + DataHandler() DataHandler + StateFetcher() *StateFetcher +} + +type DefaultServicesFactory struct { + backendService *PrivacyService + accountManager *accounts.Manager + dataHandler *JsonFileDataHandler + stateFetcher *StateFetcher +} + +func NewServicesFactory(node *node.Node, ptm private.PrivateTransactionManager, ethService *eth.Ethereum) (*DefaultServicesFactory, error) { + factory := &DefaultServicesFactory{} + + factory.accountManager = ethService.AccountManager() + factory.dataHandler = NewJsonFileDataHandler(node.InstanceDir()) + factory.stateFetcher = NewStateFetcher(ethService.BlockChain()) + + backendService, err := New(ptm, factory.AccountManager(), factory.DataHandler(), factory.StateFetcher(), ethService.APIBackend) + if err != nil { + return nil, err + } + factory.backendService = backendService + + _, isMultitenant := ethService.BlockChain().SupportsMultitenancy(context.Background()) + privacyExtension.DefaultExtensionHandler.SupportMultitenancy(isMultitenant) + + ethService.BlockChain().PopulateSetPrivateState(privacyExtension.DefaultExtensionHandler.CheckExtensionAndSetPrivateState) + + go backendService.initialise(node) + + return factory, nil +} + +func (factory *DefaultServicesFactory) BackendService() *PrivacyService { + return factory.backendService +} + +func (factory *DefaultServicesFactory) AccountManager() *accounts.Manager { + return factory.accountManager +} + +func (factory *DefaultServicesFactory) DataHandler() DataHandler { + return factory.dataHandler +} + +func (factory *DefaultServicesFactory) StateFetcher() *StateFetcher { + return factory.stateFetcher +} diff --git a/extension/state_fetcher.go b/extension/state_fetcher.go new file mode 100644 index 0000000000..37aea7ed3c --- /dev/null +++ b/extension/state_fetcher.go @@ -0,0 +1,120 @@ +package extension + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/extension/extensionContracts" + "github.com/ethereum/go-ethereum/multitenancy" + "github.com/ethereum/go-ethereum/rpc" +) + +// ChainAccessor provides methods to fetch state and blocks from the local blockchain +type ChainAccessor interface { + multitenancy.ContextAware + // GetBlockByHash retrieves a block from the local chain. + GetBlockByHash(common.Hash) *types.Block + StateAt(root common.Hash) (*state.StateDB, *state.StateDB, error) + State() (*state.StateDB, *state.StateDB, error) + CurrentBlock() *types.Block +} + +// Only extract required methods from ethService.APIBackend +type APIBackendHelper interface { + multitenancy.AuthorizationProvider + AccountExtraDataStateGetterByNumber(ctx context.Context, number rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) + CurrentBlock() *types.Block +} + +// StateFetcher manages retrieving state from the database and returning it in +// a usable form by the extension API. +type StateFetcher struct { + chainAccessor ChainAccessor +} + +// Creates a new StateFetcher from the ethereum service +func NewStateFetcher(chainAccessor ChainAccessor) *StateFetcher { + return &StateFetcher{ + chainAccessor: chainAccessor, + } +} + +// returns the current block hash +func (fetcher *StateFetcher) getCurrentBlockHash() common.Hash { + return fetcher.chainAccessor.CurrentBlock().Hash() +} + +// GetAddressStateFromBlock is a public method that combines the other +// functions of a StateFetcher, retrieving the state of an address at a given +// block, represented in JSON. +func (fetcher *StateFetcher) GetAddressStateFromBlock(blockHash common.Hash, addressToFetch common.Address) ([]byte, error) { + privateState, err := fetcher.privateState(blockHash) + if err != nil { + return nil, err + } + stateData, err := fetcher.addressStateAsJson(privateState, addressToFetch) + if err != nil { + return nil, err + } + return stateData, nil +} + +// privateState returns the private state database for a given block hash. +func (fetcher *StateFetcher) privateState(blockHash common.Hash) (*state.StateDB, error) { + block := fetcher.chainAccessor.GetBlockByHash(blockHash) + _, privateState, err := fetcher.chainAccessor.StateAt(block.Root()) + + return privateState, err +} + +// addressStateAsJson returns the state of an address, including the balance, +// nonce, code and state data as a JSON map. +func (fetcher *StateFetcher) addressStateAsJson(privateState *state.StateDB, addressToShare common.Address) ([]byte, error) { + keepAddresses := make(map[string]extensionContracts.AccountWithMetadata) + + if account, found := privateState.DumpAddress(addressToShare); found { + keepAddresses[addressToShare.Hex()] = extensionContracts.AccountWithMetadata{ + State: account, + } + } else { + return nil, fmt.Errorf("error in contract state fetch") + } + //types can be marshalled, so errors can't occur + out, _ := json.Marshal(&keepAddresses) + return out, nil +} + +// returns the privacy metadata +func (fetcher *StateFetcher) GetPrivacyMetaData(blockHash common.Hash, address common.Address) (*state.PrivacyMetadata, error) { + privateState, err := fetcher.privateState(blockHash) + if err != nil { + return nil, err + } + + privacyMetaData, err := privateState.GetPrivacyMetadata(address) + if err != nil { + return nil, err + } + + return privacyMetaData, nil +} + +// returns the privacy metadata +func (fetcher *StateFetcher) GetStorageRoot(blockHash common.Hash, address common.Address) (common.Hash, error) { + privateState, err := fetcher.privateState(blockHash) + if err != nil { + return common.Hash{}, err + } + + storageRoot, err := privateState.GetStorageRoot(address) + if err != nil { + return common.Hash{}, err + } + + return storageRoot, nil +} diff --git a/extension/state_fetcher_test.go b/extension/state_fetcher_test.go new file mode 100644 index 0000000000..86e726b555 --- /dev/null +++ b/extension/state_fetcher_test.go @@ -0,0 +1,49 @@ +package extension + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" +) + +func TestDumpAddressWhenFound(t *testing.T) { + //db := ethdb.NewMemDatabase() + db := rawdb.NewMemoryDatabase() + + statedb, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + address := common.HexToAddress("0x2222222222222222222222222222222222222222") + + stateFetcher := NewStateFetcher(nil) + + // generate a few entries and write them out to the db + statedb.SetBalance(address, big.NewInt(22)) + statedb.SetCode(address, []byte{3, 3, 3, 3, 3, 3, 3}) + statedb.Commit(false) + + out, _ := stateFetcher.addressStateAsJson(statedb, address) + + want := `{"0x2222222222222222222222222222222222222222":{"state":{"balance":"22","nonce":0,"root":"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3","code":"03030303030303"}}}` + + if string(out) != want { + t.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", string(out), want) + } +} + +func TestDumpAddressWhenNotFound(t *testing.T) { + //db := ethdb.NewMemDatabase() + db := rawdb.NewMemoryDatabase() + statedb, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + statedb.Commit(false) + + stateFetcher := NewStateFetcher(nil) + + address := common.HexToAddress("0x2222222222222222222222222222222222222222") + out, _ := stateFetcher.addressStateAsJson(statedb, address) + + if out != nil { + t.Errorf("dump mismatch:\ngot: %s\nwant: nil\n", string(out)) + } +} diff --git a/extension/types.go b/extension/types.go new file mode 100644 index 0000000000..c68e3a3409 --- /dev/null +++ b/extension/types.go @@ -0,0 +1,40 @@ +package extension + +import ( + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/extension/extensionContracts" +) + +var ( + //Log queries + newExtensionQuery = ethereum.FilterQuery{ + FromBlock: nil, + ToBlock: nil, + Topics: [][]common.Hash{{common.HexToHash(extensionContracts.NewContractExtensionContractCreatedTopicHash)}}, + Addresses: []common.Address{}, + } + + finishedExtensionQuery = ethereum.FilterQuery{ + FromBlock: nil, + ToBlock: nil, + Topics: [][]common.Hash{{common.HexToHash(extensionContracts.ExtensionFinishedTopicHash)}}, + Addresses: []common.Address{}, + } + + canPerformStateShareQuery = ethereum.FilterQuery{ + FromBlock: nil, + ToBlock: nil, + Topics: [][]common.Hash{{common.HexToHash(extensionContracts.CanPerformStateShareTopicHash)}}, + Addresses: []common.Address{}, + } +) + +type ExtensionContract struct { + ContractExtended common.Address `json:"contractExtended"` + Initiator common.Address `json:"initiator"` + Recipient common.Address `json:"recipient"` + ManagementContractAddress common.Address `json:"managementContractAddress"` + RecipientPtmKey string `json:"recipientPtmKey"` + CreationData []byte `json:"creationData"` +} diff --git a/go.mod b/go.mod index 12d0f4c9d9..634c85aca0 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,20 @@ module github.com/ethereum/go-ethereum -go 1.13 +go 1.15 + +// Quorum - Replace Go modules that use modifications done by us + +replace github.com/ethereum/go-ethereum/crypto/secp256k1 => github.com/jpmorganchase/quorum/crypto/secp256k1 v0.0.0-20200804194033-c8f07379f487 + +replace github.com/coreos/etcd => github.com/Consensys/etcd v3.3.13-quorum197+incompatible + +// End Quorum require ( github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect + github.com/BurntSushi/toml v0.3.1 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 @@ -13,37 +22,51 @@ require ( github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 + github.com/coreos/etcd v3.3.20+incompatible + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87 + github.com/eapache/channels v1.1.0 + github.com/eapache/queue v1.1.0 // indirect github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c - github.com/fatih/color v1.3.0 + github.com/ethereum/go-ethereum/crypto/secp256k1 v0.0.0-00010101000000-000000000000 + github.com/fatih/color v1.7.0 github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 - github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c + github.com/golang/mock v1.4.3 + github.com/golang/protobuf v1.3.4 github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 github.com/google/go-cmp v0.3.1 // indirect github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 + github.com/hashicorp/go-hclog v0.13.0 + github.com/hashicorp/go-plugin v1.2.2 github.com/hashicorp/golang-lru v0.5.4 github.com/holiman/uint256 v1.1.0 github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 + github.com/jpmorganchase/quorum-account-plugin-sdk-go v0.0.0-20200714175524-662195b38a5e + github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go v0.0.0-20200210211148-57f99f69eeb3 + github.com/jpmorganchase/quorum-security-plugin-sdk-go v0.0.0-20200714173835-22a319bb78ce github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 github.com/kr/pretty v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.0 - github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 + github.com/mattn/go-colorable v0.1.4 + github.com/mattn/go-isatty v0.0.10 github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c + github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 @@ -56,16 +79,22 @@ require ( github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d + github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f + golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/text v0.3.2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 + google.golang.org/grpc v1.29.1 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 + gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce - gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 + gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 + gopkg.in/oleiade/lane.v1 v1.0.0 gopkg.in/urfave/cli.v1 v1.20.0 gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index f3b5178f6a..185320d791 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= @@ -19,7 +20,10 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Consensys/etcd v3.3.13-quorum197+incompatible h1:ZBM9sH4QEufgaShSyNNhffuZv6Zhl5kyD2b/NHViByM= +github.com/Consensys/etcd v3.3.13-quorum197+incompatible/go.mod h1:wz4o/jwsTgMkSZUY9DmwVEIL3b2JX3t+tCDdy/J5ilY= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -33,17 +37,27 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4 github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -59,10 +73,18 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87 h1:OMbqMXf9OAXzH1dDH82mQMrddBE8LIIwDtxeK4wE1/A= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= +github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= -github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -79,22 +101,39 @@ github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIi github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c h1:zqAKixg3cTcIasAMJV+EcfVbWwLpOZ7LeoWJvcuD/5Q= -github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.13.0 h1:Do32YnDMnq7v7FU50AgH+1ExKCOkl9HBxvSI1JWr+rA= +github.com/hashicorp/go-hclog v0.13.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.2.2 h1:mgDpq0PkoK5gck2w4ivaMpWRHv/matdOR4xmeScmf/w= +github.com/hashicorp/go-plugin v1.2.2/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/holiman/uint256 v1.1.0 h1:Iye6ze0DW9s+7EMn8y6Q4ebegDzpu28JQHEVM1Bq+Wg= github.com/holiman/uint256 v1.1.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -106,12 +145,23 @@ github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jpmorganchase/quorum-account-plugin-sdk-go v0.0.0-20200714175524-662195b38a5e h1:aE+TcHdEop381e8gMBWw/7Nw5aOdXdVmVrVP7ZrKrq4= +github.com/jpmorganchase/quorum-account-plugin-sdk-go v0.0.0-20200714175524-662195b38a5e/go.mod h1:clocsx5vZHANnLM+SmcJJDKY6VVxxcdRUCKe5Y+roQ0= +github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go v0.0.0-20200210211148-57f99f69eeb3 h1:vaXIQlaE9ELd0V5NjwMHD8hlAPeC2A1Zu1i2TxKdpVY= +github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go v0.0.0-20200210211148-57f99f69eeb3/go.mod h1:xZOd/PmBeJkF1X+VLaIkCsQq1nqlr2DiekzGMpBDZq0= +github.com/jpmorganchase/quorum-security-plugin-sdk-go v0.0.0-20200714173835-22a319bb78ce h1:N0BFCITB+CS2fwTlnYuwr9KslnVWxpz7rs8xyyhS1xA= +github.com/jpmorganchase/quorum-security-plugin-sdk-go v0.0.0-20200714173835-22a319bb78ce/go.mod h1:Zq2sOjX+LZrNoV+cyvS/4Xsy69v8HOFKHtCLkiXQ3Kk= +github.com/jpmorganchase/quorum/crypto/secp256k1 v0.0.0-20200804194033-c8f07379f487 h1:xEt7bIjWO384fz4pz9ZcS3CMG2hUgPwgLb180jeqLzs= +github.com/jpmorganchase/quorum/crypto/secp256k1 v0.0.0-20200804194033-c8f07379f487/go.mod h1:w+wA+9W4bxqH3Jg8upYxYo8MYlQL0UOXJfKVSr+45Ok= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -121,21 +171,27 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk= @@ -147,6 +203,8 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= @@ -156,9 +214,14 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce h1:X0jFYGnHemYDIW6jlc+fSI8f9Cg+jqCnClYP2WgZT/A= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -179,6 +242,7 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -186,44 +250,88 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw= +gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/oleiade/lane.v1 v1.0.0 h1:Xs7/GTdnZNGuuXV7z8gTrHoHEeHdCnhuNXm4n0UpxjY= +gopkg.in/oleiade/lane.v1 v1.0.0/go.mod h1:e9mCiNjxfTGlkjxn/TPK3JUwhjKjby5cjXuGotH/QlE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= @@ -233,3 +341,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/graphql/graphql.go b/graphql/graphql.go index 1479ae7fdb..712af52f19 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -20,17 +20,16 @@ package graphql import ( "context" "errors" - "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/private" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -47,9 +46,9 @@ type Account struct { } // getState fetches the StateDB object for an account. -func (a *Account) getState(ctx context.Context) (*state.StateDB, error) { - state, _, err := a.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash) - return state, err +func (a *Account) getState(ctx context.Context) (vm.MinimalApiState, error) { + stat, _, err := a.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash) + return stat, err } func (a *Account) Address(ctx context.Context) (common.Address, error) { @@ -314,6 +313,35 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { return &ret, nil } +// Quorum +func (t *Transaction) IsPrivate(ctx context.Context) (*bool, error) { + ret := false + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return &ret, err + } + ret = tx.IsPrivate() + return &ret, nil +} + +func (t *Transaction) PrivateInputData(ctx context.Context) (*hexutil.Bytes, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return &hexutil.Bytes{}, err + } + if tx.IsPrivate() { + _, _, privateInputData, _, err := private.P.Receive(common.BytesToEncryptedPayloadHash(tx.Data())) + if err != nil || tx == nil { + return &hexutil.Bytes{}, err + } + ret := hexutil.Bytes(privateInputData) + return &ret, nil + } + return &hexutil.Bytes{}, nil +} + +// END QUORUM + func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) { tx, err := t.resolve(ctx) if err != nil || tx == nil { @@ -803,7 +831,9 @@ func (b *Block) Call(ctx context.Context, args struct { return nil, err } } - result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) + + // Quorum - replaced the default 5s time out with the value passed in vm.calltimeout + result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, b.backend.CallTimeOut(), b.backend.RPCGasCap()) if err != nil { return nil, err } @@ -873,7 +903,9 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.CallArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) + + // Quorum - replaced the default 5s time out with the value passed in vm.calltimeout + result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, p.backend.CallTimeOut(), p.backend.RPCGasCap()) if err != nil { return nil, err } @@ -988,7 +1020,7 @@ func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hex if err := rlp.DecodeBytes(args.Data, tx); err != nil { return common.Hash{}, err } - hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx) + hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx, "", nil, true) return hash, err } diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 40b13187f4..32ddf3a1ef 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -17,7 +17,15 @@ package graphql import ( + "context" + "math/big" "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/private/engine/notinuse" ) func TestBuildSchema(t *testing.T) { @@ -26,3 +34,83 @@ func TestBuildSchema(t *testing.T) { t.Errorf("Could not construct GraphQL handler: %v", err) } } + +// Quorum +// Test Quorum specific GraphQL schema for private transaction +func TestQuorumSchema(t *testing.T) { + saved := private.P + defer func() { + private.P = saved + }() + arbitraryPayloadHash := common.BytesToEncryptedPayloadHash([]byte("arbitrary key")) + private.P = &StubPrivateTransactionManager{ + responses: map[common.EncryptedPayloadHash][]interface{}{ + arbitraryPayloadHash: { + []byte("private payload"), // equals to 0x70726976617465207061796c6f6164 after converting to bytes + nil, + }, + }, + } + // Test private transaction + privateTx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), arbitraryPayloadHash.Bytes()) + privateTx.SetPrivate() + privateTxQuery := &Transaction{tx: privateTx} + isPrivate, err := privateTxQuery.IsPrivate(context.Background()) + if err != nil { + t.Fatalf("Expect no error: %v", err) + } + if !*isPrivate { + t.Fatalf("Expect isPrivate to be true for private TX") + } + privateInputData, err := privateTxQuery.PrivateInputData(context.Background()) + if err != nil { + t.Fatalf("Expect no error: %v", err) + } + if privateInputData.String() != "0x70726976617465207061796c6f6164" { + t.Fatalf("Expect privateInputData to be: \"0x70726976617465207061796c6f6164\" for private TX, actual: %v", privateInputData.String()) + } + // Test public transaction + publicTx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte("key")) + publicTxQuery := &Transaction{tx: publicTx} + isPrivate, err = publicTxQuery.IsPrivate(context.Background()) + if err != nil { + t.Fatalf("Expect no error: %v", err) + } + if *isPrivate { + t.Fatalf("Expect isPrivate to be false for public TX") + } + privateInputData, err = publicTxQuery.PrivateInputData(context.Background()) + if err != nil { + t.Fatalf("Expect no error: %v", err) + } + if privateInputData.String() != "0x" { + t.Fatalf("Expect privateInputData to be: \"0x\" for public TX, actual: %v", privateInputData.String()) + } +} + +type StubPrivateTransactionManager struct { + notinuse.PrivateTransactionManager + responses map[common.EncryptedPayloadHash][]interface{} +} + +func (spm *StubPrivateTransactionManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return true +} + +func (spm *StubPrivateTransactionManager) Receive(txHash common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) { + res := spm.responses[txHash] + if err, ok := res[1].(error); ok { + return "", nil, nil, nil, err + } + if ret, ok := res[0].([]byte); ok { + return "", nil, ret, &engine.ExtraMetadata{ + PrivacyFlag: engine.PrivacyFlagStandardPrivate, + }, nil + } + return "", nil, nil, nil, nil +} + +func (spm *StubPrivateTransactionManager) ReceiveRaw(hash common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) { + _, sender, data, metadata, err := spm.Receive(hash) + return data, sender[0], metadata, err +} diff --git a/graphql/schema.go b/graphql/schema.go index 5dec10db20..669d951986 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -115,6 +115,10 @@ const schema string = ` # Logs is a list of log entries emitted by this transaction. If the # transaction has not yet been mined, this field will be null. logs: [Log!] + # IsPrivate is an indicator of Quorum private transaction + isPrivate: Boolean + # PrivateInputData is the actual payload of Quorum private transaction + privateInputData: Bytes r: BigInt! s: BigInt! v: BigInt! diff --git a/internal/build/gosrc.go b/internal/build/gosrc.go new file mode 100644 index 0000000000..c85e469680 --- /dev/null +++ b/internal/build/gosrc.go @@ -0,0 +1,81 @@ +// Copyright 2019 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 . + +package build + +import ( + "bytes" + "crypto/sha256" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" +) + +// EnsureGoSources ensures that path contains a file with the given SHA256 hash, +// and if not, it downloads a fresh Go source package from upstream and replaces +// path with it (if the hash matches). +func EnsureGoSources(version string, hash []byte, path string) error { + // Sanity check the destination path to ensure we don't do weird things + if !strings.HasSuffix(path, ".tar.gz") { + return fmt.Errorf("destination path (%s) must end with .tar.gz", path) + } + // If the file exists, validate it's hash + if archive, err := ioutil.ReadFile(path); err == nil { // Go sources are ~20MB, it's fine to read all + hasher := sha256.New() + hasher.Write(archive) + have := hasher.Sum(nil) + + if bytes.Equal(have, hash) { + fmt.Printf("Go %s [%x] available at %s\n", version, hash, path) + return nil + } + fmt.Printf("Go %s hash mismatch (have %x, want %x) at %s, deleting old archive\n", version, have, hash, path) + if err := os.Remove(path); err != nil { + return err + } + } + // Archive missing or bad hash, download a new one + fmt.Printf("Downloading Go %s [want %x] into %s\n", version, hash, path) + + res, err := http.Get(fmt.Sprintf("https://dl.google.com/go/go%s.src.tar.gz", version)) + if err != nil || res.StatusCode != http.StatusOK { + return fmt.Errorf("failed to access Go sources: code %d, err %v", res.StatusCode, err) + } + defer res.Body.Close() + + archive, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + // Sanity check the downloaded archive, save if checks out + hasher := sha256.New() + hasher.Write(archive) + + if have := hasher.Sum(nil); !bytes.Equal(have, hash) { + return fmt.Errorf("downloaded Go %s hash mismatch (have %x, want %x)", version, have, hash) + } + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + if err := ioutil.WriteFile(path, archive, 0644); err != nil { + return err + } + fmt.Printf("Downloaded Go %s [%x] into %s\n", version, hash, path) + return nil +} diff --git a/internal/build/util.go b/internal/build/util.go index fc559760b2..785abd3139 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -152,3 +152,53 @@ func UploadSFTP(identityFile, host, dir string, files []string) error { stdin.Close() return sftp.Wait() } + +// ExpandPackagesNoVendor expands a cmd/go import path pattern, skipping +// vendored packages. +func ExpandPackagesNoVendor(patterns []string) []string { + expand := false + for _, pkg := range patterns { + if strings.Contains(pkg, "...") { + expand = true + } + } + if expand { + cmd := GoTool("list", patterns...) + out, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("package listing failed: %v\n%s", err, string(out)) + } + var packages []string + for _, line := range strings.Split(string(out), "\n") { + if !strings.Contains(line, "/vendor/") { + packages = append(packages, strings.TrimSpace(line)) + } + } + return packages + } + return patterns +} + +// Read QUORUM_IGNORE_TEST_PACKAGES env and remove from packages +func IgnorePackages(packages []string) []string { + ignore := os.Getenv("QUORUM_IGNORE_TEST_PACKAGES") + if ignore == "" { + return packages + } + ret := make([]string, 0, len(packages)) + ignorePackages := strings.Split(ignore, ",") + + for _, p := range packages { + mustInclude := true + for _, ig := range ignorePackages { + if strings.Index(p, strings.TrimSpace(ig)) == 0 { + mustInclude = false + break + } + } + if mustInclude { + ret = append(ret, p) + } + } + return ret +} diff --git a/internal/build/util_test.go b/internal/build/util_test.go new file mode 100644 index 0000000000..03094a5499 --- /dev/null +++ b/internal/build/util_test.go @@ -0,0 +1,40 @@ +package build + +import ( + "os" + "testing" + + testifyassert "github.com/stretchr/testify/assert" +) + +func TestIgnorePackages_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + arbitraryPackages := []string{"abc", "xyz/abc"} + + actual := IgnorePackages(arbitraryPackages) + + assert.Equal(arbitraryPackages, actual) +} + +func TestIgnorePackages_whenIgnoreOnePackage(t *testing.T) { + assert := testifyassert.New(t) + + arbitraryPackages := []string{"abc", "xyz/abc"} + assert.NoError(os.Setenv("QUORUM_IGNORE_TEST_PACKAGES", "abc")) + + actual := IgnorePackages(arbitraryPackages) + + assert.Equal([]string{arbitraryPackages[1]}, actual) +} + +func TestIgnorePackages_whenIgnorePackages(t *testing.T) { + assert := testifyassert.New(t) + + arbitraryPackages := []string{"abc", "xyz/abc/opq"} + assert.NoError(os.Setenv("QUORUM_IGNORE_TEST_PACKAGES", "abc, xyz/abc")) + + actual := IgnorePackages(arbitraryPackages) + + assert.Len(actual, 0) +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c035b7a9ed..d15b213114 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -19,16 +19,21 @@ package ethapi import ( "bytes" "context" + "encoding/hex" + "encoding/json" "errors" "fmt" "math/big" + "net/http" "strings" + "sync" "time" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/pluggable" "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -36,17 +41,35 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" "github.com/tyler-smith/go-bip39" ) +const ( + //Hex-encoded 64 byte array of "17" values + maxPrivateIntrinsicDataHex = "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" +) + +type TransactionType uint8 + +const ( + FillTransaction TransactionType = iota + 1 + RawTransaction + NormalTransaction +) + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -333,23 +356,57 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre } else { d = time.Duration(*duration) * time.Second } - ks, err := fetchKeystore(s.am) - if err != nil { - return false, err - } - err = ks.TimedUnlock(accounts.Account{Address: addr}, password, d) + err := s.unlockAccount(addr, password, d) if err != nil { log.Warn("Failed account unlock attempt", "address", addr, "err", err) } return err == nil, err } +func (s *PrivateAccountAPI) unlockAccount(addr common.Address, password string, duration time.Duration) error { + acct := accounts.Account{Address: addr} + + backend, err := s.am.Backend(acct) + if err != nil { + return err + } + + switch b := backend.(type) { + case *pluggable.Backend: + return b.TimedUnlock(acct, password, duration) + case *keystore.KeyStore: + return b.TimedUnlock(acct, password, duration) + default: + return errors.New("unlock only supported for keystore or plugin wallets") + } +} + // LockAccount will lock the account associated with the given address when it's unlocked. func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { - if ks, err := fetchKeystore(s.am); err == nil { - return ks.Lock(addr) == nil + if err := s.lockAccount(addr); err != nil { + log.Warn("Failed account lock attempt", "address", addr, "err", err) + return false + } + + return true +} + +func (s *PrivateAccountAPI) lockAccount(addr common.Address) error { + acct := accounts.Account{Address: addr} + + backend, err := s.am.Backend(acct) + if err != nil { + return err + } + + switch b := backend.(type) { + case *pluggable.Backend: + return b.Lock(acct) + case *keystore.KeyStore: + return b.Lock(addr) + default: + return errors.New("lock only supported for keystore or plugin wallets") } - return false } // signTransaction sets defaults and signs the given transaction @@ -369,7 +426,17 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg // Assemble the transaction and sign with the wallet tx := args.toTransaction() - return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID) + // Quorum + if args.IsPrivate() { + tx.SetPrivate() + } + var chainID *big.Int + if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) && !tx.IsPrivate() { + chainID = config.ChainID + } + // /Quorum + + return wallet.SignTxWithPassphrase(account, passwd, tx, chainID) } // SendTransaction will create a transaction from the given arguments and @@ -382,12 +449,29 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs s.nonceLock.LockAddr(args.From) defer s.nonceLock.UnlockAddr(args.From) } + + // Set some sanity defaults and terminate on failure + if err := args.setDefaults(ctx, s.b); err != nil { + return common.Hash{}, err + } + + // Quorum + isPrivate, data, err := checkAndHandlePrivateTransaction(ctx, s.b, args.toTransaction(), &args.PrivateTxArgs, args.From, NormalTransaction) + if err != nil { + return common.Hash{}, err + } + if isPrivate && !common.EmptyEncryptedPayloadHash(data) { + // replace the original payload with encrypted payload hash + args.Data = data.BytesTypeRef() + } + // /Quorum + signed, err := s.signTransaction(ctx, &args, passwd) if err != nil { log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) return common.Hash{}, err } - return SubmitTransaction(ctx, s.b, signed) + return SubmitTransaction(ctx, s.b, signed, args.PrivateFrom, args.PrivateFor, false) } // SignTransaction will create a transaction from the given arguments and @@ -814,6 +898,9 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } +// Quorum - Multitenancy +// Before returning the result, we need to inspect the EVM and +// perform verification check func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) @@ -861,9 +948,39 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // this makes sure resources are cleaned up. defer cancel() - // Get a new instance of the EVM. msg := args.ToMessage(globalGasCap) - evm, vmError, err := b.GetEVM(ctx, msg, state, header) + + enrichedCtx := ctx + // create callbacks to support runtime multitenancy checks during the run + if authToken, ok := b.SupportsMultitenancy(ctx); ok { + var authorizeMessageCallFunc multitenancy.AuthorizeMessageCallFunc = func(contractAddress common.Address) (bool, bool, error) { + var readSecAttr *multitenancy.ContractSecurityAttribute + if len(msg.Data()) == 0 { // public READ + readSecAttr = multitenancy.NewContractSecurityAttributeBuilder().FromEOA(msg.From()).ToEOA(*msg.To()).Public().Read().Build() + } else { + currentBlock := b.CurrentBlock().Number().Int64() + extraDataReader, err := b.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(currentBlock)) + if err != nil { + return false, false, fmt.Errorf("no account extra data reader at block %v: %w", currentBlock, err) + } + managedParties, err := extraDataReader.GetManagedParties(contractAddress) + isPrivate := true + if errors.Is(err, common.ErrNotPrivateContract) { + isPrivate = false + } else if err != nil { + return false, false, fmt.Errorf("%s not found in the index, error: %s", contractAddress.Hex(), err.Error()) + } + readSecAttr = multitenancy.NewContractSecurityAttributeBuilder().FromEOA(msg.From()).PrivateIf(isPrivate).PartiesOnlyIf(isPrivate, managedParties).Read().Build() + } + authorizedRead, _ := b.IsAuthorized(ctx, authToken, readSecAttr) + log.Trace("Authorized Message Call", "read", authorizedRead, "address", contractAddress.Hex(), "securityAttribute", readSecAttr) + return authorizedRead, false, nil + } + enrichedCtx = context.WithValue(enrichedCtx, multitenancy.CtxKeyAuthorizeMessageCallFunc, authorizeMessageCallFunc) + } + + // Get a new instance of the EVM. + evm, vmError, err := b.GetEVM(enrichedCtx, msg, state, header) if err != nil { return nil, err } @@ -877,7 +994,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // Setup the gas pool (also for unmetered requests) // and apply the message. gp := new(core.GasPool).AddGas(math.MaxUint64) - result, err := core.ApplyMessage(evm, msg, gp) + result, applyErr := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { return nil, err } @@ -885,8 +1002,8 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo if evm.Cancelled() { return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - if err != nil { - return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas()) + if applyErr != nil { + return result, fmt.Errorf("err: %w (supplied gas %d)", applyErr, msg.Gas()) } return result, nil } @@ -927,12 +1044,16 @@ func (e *revertError) ErrorData() interface{} { // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. +// Quorum +// - replaced the default 5s time out with the value passed in vm.calltimeout +// - multi tenancy verification func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) { var accounts map[common.Address]account if overrides != nil { accounts = *overrides } - result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) + + result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, s.b.CallTimeOut(), s.b.RPCGasCap()) if err != nil { return nil, err } @@ -1046,6 +1167,39 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) } } + + //QUORUM + + //We don't know if this is going to be a private or public transaction + //It is possible to have a data field that has a lower intrinsic value than the PTM hash + //so this checks that if we were to place a PTM hash (with all non-zero values) here then the transaction would + //still run + //This makes the return value a potential over-estimate of gas, rather than the exact cost to run right now + + //if the transaction has a value then it cannot be private, so we can skip this check + if args.Value != nil && args.Value.ToInt().Cmp(big.NewInt(0)) == 0 { + homestead := b.ChainConfig().IsHomestead(new(big.Int).SetInt64(int64(rpc.PendingBlockNumber))) + istanbul := b.ChainConfig().IsIstanbul(new(big.Int).SetInt64(int64(rpc.PendingBlockNumber))) + var data []byte + if args.Data == nil { + data = nil + } else { + data = []byte(*args.Data) + } + intrinsicGasPublic, _ := core.IntrinsicGas(data, args.To == nil, homestead, istanbul) + intrinsicGasPrivate, _ := core.IntrinsicGas(common.Hex2Bytes(maxPrivateIntrinsicDataHex), args.To == nil, homestead, istanbul) + + if intrinsicGasPrivate > intrinsicGasPublic { + if math.MaxUint64-hi < intrinsicGasPrivate-intrinsicGasPublic { + return 0, fmt.Errorf("private intrinsic gas addition exceeds allowance") + } + return hexutil.Uint64(hi + (intrinsicGasPrivate - intrinsicGasPublic)), nil + } + + } + + //END QUORUM + return hexutil.Uint64(hi), nil } @@ -1218,8 +1372,9 @@ type RPCTransaction struct { // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { + var signer types.Signer = types.HomesteadSigner{} + // joel: this is one of the two places we used a wrong signer to print txes + if tx.Protected() && !tx.IsPrivate() { signer = types.NewEIP155Signer(tx.ChainId()) } from, _ := types.Sender(signer, tx) @@ -1360,6 +1515,17 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr return (*hexutil.Uint64)(&nonce), state.Error() } +// Quorum +func (s *PublicTransactionPoolAPI) GetContractPrivacyMetadata(ctx context.Context, address common.Address) (*state.PrivacyMetadata, error) { + state, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if state == nil || err != nil { + return nil, err + } + return state.GetPrivacyMetadata(address) +} + +// /Quorum + // GetTransactionByHash returns the transaction for the given hash func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) { // Try to return an already finalized transaction @@ -1411,8 +1577,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha } receipt := receipts[index] - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { + var signer types.Signer = types.HomesteadSigner{} + if tx.Protected() && !tx.IsPrivate() { signer = types.NewEIP155Signer(tx.ChainId()) } from, _ := types.Sender(signer, tx) @@ -1444,9 +1610,44 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha if receipt.ContractAddress != (common.Address{}) { fields["contractAddress"] = receipt.ContractAddress } + if authToken, ok := s.b.SupportsMultitenancy(ctx); ok { + extraDataReader, err := s.b.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(blockNumber)) + if err != nil { + return nil, fmt.Errorf("no account extra data reader at block %v: %w", blockNumber, err) + } + + filteredLogs := make([]*types.Log, 0) + for _, l := range receipt.Logs { + ok, err := s.isContractAuthorized(ctx, authToken, extraDataReader, l.Address) + if err != nil { + return nil, err + } + if ok { + filteredLogs = append(filteredLogs, l) + } + } + fields["logs"] = filteredLogs + receiptClone := &types.Receipt{PostState: receipt.PostState, Status: receipt.Status, Logs: filteredLogs} + fields["logsBloom"] = types.CreateBloom(types.Receipts{receiptClone}) + } return fields, nil } +func (s *PublicTransactionPoolAPI) isContractAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, extraDataReader vm.AccountExtraDataStateGetter, addr common.Address) (bool, error) { + attrBuilder := multitenancy.NewContractSecurityAttributeBuilder().Read().Private() + managedParties, err := extraDataReader.GetManagedParties(addr) + if errors.Is(err, common.ErrNotPrivateContract) { + attrBuilder.Public() + } else if err != nil { + return false, fmt.Errorf("contract %s not found in the index due to %s", addr.Hex(), err.Error()) + } + + ok, _ := s.b.IsAuthorized(ctx, authToken, attrBuilder.Parties(managedParties).Build()) + + return ok, nil +} + +// Quorum: if signing a private TX, set with tx.SetPrivate() before calling this method. // sign is a helper function that signs a transaction with the private key of the given address. func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { // Look up the wallet containing the requested signer @@ -1456,12 +1657,24 @@ func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transacti if err != nil { return nil, err } + + // Quorum + var chainID *big.Int + if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) && !tx.IsPrivate() { + chainID = config.ChainID + } + // /Quorum + // Request the wallet to sign the transaction - return wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) + return wallet.SignTx(account, tx, chainID) } // SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool. +// Quorum: introducing additional arguments encapsulated in PrivateTxArgs struct +// to support private transactions processing. type SendTxArgs struct { + PrivateTxArgs // Quorum + From common.Address `json:"from"` To *common.Address `json:"to"` Gas *hexutil.Uint64 `json:"gas"` @@ -1474,6 +1687,29 @@ type SendTxArgs struct { Input *hexutil.Bytes `json:"input"` } +func (s SendTxArgs) IsPrivate() bool { + return s.PrivateFor != nil +} + +// SendRawTxArgs represents the arguments to submit a new signed private transaction into the transaction pool. +type SendRawTxArgs struct { + PrivateTxArgs +} + +// Additional arguments used in private transactions +type PrivateTxArgs struct { + // PrivateFrom is the public key of the sending party. + // The public key must be available in the Private Transaction Manager (i.e.: Tessera) which is paired with this geth node. + // Empty value means the Private Transaction Manager will use the first public key + // in its list of available keys which it maintains. + 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"` +} + // setDefaults is a helper function that fills in default values for unspecified tx fields. func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { if args.GasPrice == nil { @@ -1531,44 +1767,248 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { args.Gas = &estimated log.Trace("Estimate gas usage automatically", "gas", args.Gas) } + //Quorum + if args.PrivateTxType == "" { + args.PrivateTxType = "restricted" + } + //End-Quorum return nil } -func (args *SendTxArgs) toTransaction() *types.Transaction { - var input []byte +func (args *SendTxArgs) inputOrData() (result []byte) { + // TODO - check if input (instead of data) is provided whether private transactions are successful if args.Input != nil { - input = *args.Input + result = *args.Input } else if args.Data != nil { - input = *args.Data + result = *args.Data } + return result +} + +func (args *SendTxArgs) toTransaction() *types.Transaction { if args.To == nil { - return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), args.inputOrData()) } - return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), args.inputOrData()) } +// TODO: this submits a signed transaction, if it is a signed private transaction that should already be recorded in the tx. // SubmitTransaction is a helper function that submits tx to txPool and logs a message. -func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { +func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction, privateFrom string, privateFor []string, isRaw bool) (common.Hash, error) { // If the transaction fee cap is already specified, ensure the // fee of the given transaction is _reasonable_. if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil { return common.Hash{}, err } - if err := b.SendTx(ctx, tx); err != nil { + // Quorum + var signer types.Signer + if tx.IsPrivate() { + signer = types.QuorumPrivateTxSigner{} + } else { + signer = types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) + } + from, err := types.Sender(signer, tx) + if err != nil { return common.Hash{}, err } - if tx.To() == nil { - signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) - from, err := types.Sender(signer, tx) + if authToken, ok := b.SupportsMultitenancy(ctx); ok { + originalTx := tx + // for private transaction, private payload will be retrieved from Tessera to build the original transaction + // in order to supply to the simulation engine + if tx.IsPrivate() { + if isRaw { + // for raw private transaction, the privateFrom will be derived when retrieving the private payload from Tessera + originalTx, privateFrom, err = buildPrivateTransactionFromRaw(tx) + if err != nil { + return common.Hash{}, err + } + } else { + originalTx, err = buildPrivateTransaction(tx) + if err != nil { + return common.Hash{}, err + } + } + originalTx.SetPrivate() + // enforcing privateFrom present + if privateFrom == "" { + return common.Hash{}, fmt.Errorf("missing privateFrom") + } + } + err := performMultitenancyChecks(ctx, authToken, b, from, originalTx, &PrivateTxArgs{ + PrivateFrom: privateFrom, + PrivateFor: privateFor, + }) if err != nil { return common.Hash{}, err } + } + // End Quorum + + if err := b.SendTx(ctx, tx); err != nil { + return common.Hash{}, err + } + if tx.To() == nil { addr := crypto.CreateAddress(from, tx.Nonce()) - log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) + log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "to", addr.Hex()) + log.EmitCheckpoint(log.TxCreated, "tx", tx.Hash().Hex(), "to", addr.Hex()) } else { log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To()) + log.EmitCheckpoint(log.TxCreated, "tx", tx.Hash().Hex(), "to", tx.To().Hex()) } return tx.Hash(), nil + +} + +// Quorum +// +// performMultitenancyChecks is to use the given transaction and construct +// expected security attributes being checked against entitled ones. The +// transaction is fed into the simulation engine in order to determine the impact. +func performMultitenancyChecks(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, + b Backend, fromEOA common.Address, tx *types.Transaction, privateArgs *PrivateTxArgs) error { + + if tx.IsPrivate() { + // before running simulation, we verify the ownership of privateFrom + // user must be entitled for all actions + // READ and WRITE actions are taking Parties into consideration so + // we need to populate it with privateFrom + if authorized, _ := b.IsAuthorized(ctx, authToken, multitenancy.FullAccessContractSecurityAttributes(fromEOA, privateArgs.PrivateFrom)...); !authorized { + return multitenancy.ErrNotAuthorized + } + } + currentBlock := b.CurrentBlock().Number().Int64() + extraDataReader, err := b.AccountExtraDataStateGetterByNumber(ctx, rpc.BlockNumber(currentBlock)) + if err != nil { + return fmt.Errorf("no account extra data reader at block %v: %w", currentBlock, err) + } + // create callbacks to support runtime multitenancy checks during simulation + createContractSA := multitenancy.NewContractSecurityAttributeBuilder(). + FromEOA(fromEOA).PrivateIf(tx.IsPrivate()).Create().PrivateFromOnlyIf(tx.IsPrivate(), privateArgs.PrivateFrom).Build() + authorizedCreate, _ := b.IsAuthorized(ctx, authToken, createContractSA) + log.Debug("Authorized Contract Creation", "create", authorizedCreate, "securityAttribute", createContractSA) + var authorizeCreateFunc multitenancy.AuthorizeCreateFunc = func() bool { + return authorizedCreate + } + var authorizeMessageCallFunc multitenancy.AuthorizeMessageCallFunc = func(contractAddress common.Address) (bool, bool, error) { + managedParties, err := extraDataReader.GetManagedParties(contractAddress) + isPrivate := true + if errors.Is(err, common.ErrNotPrivateContract) { + isPrivate = false + } else if err != nil { + return false, false, fmt.Errorf("%s not found in the index, error: %s", contractAddress.Hex(), err.Error()) + } + readSecAttr := multitenancy.NewContractSecurityAttributeBuilder().FromEOA(fromEOA).PrivateIf(isPrivate).PartiesOnlyIf(isPrivate, managedParties).Read().Build() + authorizedRead, _ := b.IsAuthorized(ctx, authToken, readSecAttr) + log.Trace("Authorized Message Call", "read", authorizedRead, "address", contractAddress.Hex(), "securityAttribute", readSecAttr) + writeSecAttr := multitenancy.NewContractSecurityAttributeBuilder().FromEOA(fromEOA).PrivateIf(isPrivate).PartiesOnlyIf(isPrivate, managedParties).Write().Build() + authorizedWrite, _ := b.IsAuthorized(ctx, authToken, writeSecAttr) + log.Trace("Authorized Message Call", "write", authorizedWrite, "address", contractAddress.Hex(), "securityAttribute", writeSecAttr) + return authorizedRead, authorizedWrite, nil + } + enrichedCtx := ctx + enrichedCtx = context.WithValue(enrichedCtx, multitenancy.CtxKeyAuthorizeCreateFunc, authorizeCreateFunc) + enrichedCtx = context.WithValue(enrichedCtx, multitenancy.CtxKeyAuthorizeMessageCallFunc, authorizeMessageCallFunc) + if _, err := runSimulation(enrichedCtx, b, fromEOA, tx); err != nil { + log.Error("Simulated execution for multitenancy", "error", err) + return err + } + return nil +} + +// Quorum +// +// Retrieve private payload and construct the original transaction +func buildPrivateTransaction(tx *types.Transaction) (*types.Transaction, error) { + _, _, privatePayload, _, revErr := private.P.Receive(common.BytesToEncryptedPayloadHash(tx.Data())) + if revErr != nil { + return nil, revErr + } + var privateTx *types.Transaction + if tx.To() == nil { + privateTx = types.NewContractCreation(tx.Nonce(), tx.Value(), tx.Gas(), tx.GasPrice(), privatePayload) + } else { + privateTx = types.NewTransaction(tx.Nonce(), *tx.To(), tx.Value(), tx.Gas(), tx.GasPrice(), privatePayload) + } + return privateTx, nil +} + +// Quorum +// +// Retrieve private payload and construct the original transaction along with privateFrom information +func buildPrivateTransactionFromRaw(tx *types.Transaction) (*types.Transaction, string, error) { + privatePayload, privateFrom, _, revErr := private.P.ReceiveRaw(common.BytesToEncryptedPayloadHash(tx.Data())) + if revErr != nil { + return nil, "", revErr + } + var privateTx *types.Transaction + if tx.To() == nil { + privateTx = types.NewContractCreation(tx.Nonce(), tx.Value(), tx.Gas(), tx.GasPrice(), privatePayload) + } else { + privateTx = types.NewTransaction(tx.Nonce(), *tx.To(), tx.Value(), tx.Gas(), tx.GasPrice(), privatePayload) + } + return privateTx, privateFrom, nil +} + +// runSimulation runs a simulation of the given transaction. +// It returns the EVM instance upon completion +func runSimulation(ctx context.Context, b Backend, from common.Address, tx *types.Transaction) (*vm.EVM, error) { + defer func(start time.Time) { + log.Debug("Simulated Execution EVM call finished", "runtime", time.Since(start)) + }(time.Now()) + + // Set sender address or use a default if none specified + addr := from + if addr == (common.Address{}) { + if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { + if accountList := wallets[0].Accounts(); len(accountList) > 0 { + addr = accountList[0].Address + } + } + } + + // Create new call message + msg := types.NewMessage(addr, tx.To(), tx.Nonce(), tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data(), false) + + // Setup context with timeout as gas un-metered + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, time.Second*5) + // Make sure the context is cancelled when the call has completed + // this makes sure resources are cleaned up. + defer func() { cancel() }() + + // Get a new instance of the EVM. + blockNumber := b.CurrentBlock().Number().Uint64() + stateAtBlock, header, err := b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) + if stateAtBlock == nil || err != nil { + return nil, err + } + evm, _, err := b.GetEVM(ctx, msg, stateAtBlock, header) + if err != nil { + return nil, err + } + + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Done() + evm.Cancel() + }() + + var contractAddr common.Address + // even the creation of a contract (init code) can invoke other contracts + if tx.To() != nil { + // removed contract availability checks as they are performed in checkAndHandlePrivateTransaction + _, _, err = evm.Call(vm.AccountRef(addr), *tx.To(), tx.Data(), tx.Gas(), tx.Value()) + } else { + _, contractAddr, _, err = evm.Create(vm.AccountRef(addr), tx.Data(), tx.Gas(), tx.Value()) + //make sure that nonce is same in simulation as in actual block processing + //simulation blockNumber will be behind block processing blockNumber by at least 1 + //only guaranteed to work for default config where EIP158=1 + if evm.ChainConfig().IsEIP158(big.NewInt(evm.BlockNumber.Int64() + 1)) { + evm.StateDB.SetNonce(contractAddr, 1) + } + } + return evm, err } // SendTransaction creates a transaction for the given argument, sign it and submit it to the @@ -1593,14 +2033,39 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen if err := args.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } + + // Quorum + isPrivate, data, err := checkAndHandlePrivateTransaction(ctx, s.b, args.toTransaction(), &args.PrivateTxArgs, args.From, NormalTransaction) + + if err != nil { + return common.Hash{}, err + } + if isPrivate && !common.EmptyEncryptedPayloadHash(data) { + // replace the original payload with encrypted payload hash + args.Data = data.BytesTypeRef() + } + // /Quorum + // Assemble the transaction and sign with the wallet tx := args.toTransaction() - signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) + // Quorum + if args.IsPrivate() { + tx.SetPrivate() + } + // /Quorum + + var chainID *big.Int + if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) && !tx.IsPrivate() { + chainID = config.ChainID + } + // /Quorum + + signed, err := wallet.SignTx(account, tx, chainID) if err != nil { return common.Hash{}, err } - return SubmitTransaction(ctx, s.b, signed) + return SubmitTransaction(ctx, s.b, signed, args.PrivateFrom, args.PrivateFor, false) } // FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction, @@ -1611,7 +2076,25 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen return nil, err } // Assemble the transaction and obtain rlp + // Quorum + isPrivate, hash, err := checkAndHandlePrivateTransaction(ctx, s.b, args.toTransaction(), &args.PrivateTxArgs, args.From, FillTransaction) + if err != nil { + return nil, err + } + if isPrivate && !common.EmptyEncryptedPayloadHash(hash) { + // replace the original payload with encrypted payload hash + args.Data = hash.BytesTypeRef() + } + // /Quorum + tx := args.toTransaction() + + // Quorum + if isPrivate { + tx.SetPrivate() + } + // /Quorum + data, err := rlp.EncodeToBytes(tx) if err != nil { return nil, err @@ -1626,7 +2109,29 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod if err := rlp.DecodeBytes(encodedTx, tx); err != nil { return common.Hash{}, err } - return SubmitTransaction(ctx, s.b, tx) + return SubmitTransaction(ctx, s.b, tx, "", nil, false) +} + +// SendRawPrivateTransaction will add the signed transaction to the transaction pool. +// The sender is responsible for signing the transaction and using the correct nonce. +func (s *PublicTransactionPoolAPI) SendRawPrivateTransaction(ctx context.Context, encodedTx hexutil.Bytes, args SendRawTxArgs) (common.Hash, error) { + + tx := new(types.Transaction) + if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + return common.Hash{}, err + } + + // Quorum + isPrivate, _, err := checkAndHandlePrivateTransaction(ctx, s.b, tx, &args.PrivateTxArgs, common.Address{}, RawTransaction) + if err != nil { + return common.Hash{}, err + } + + if !isPrivate { + return common.Hash{}, fmt.Errorf("transaction is not private") + } + // /Quorum + return SubmitTransaction(ctx, s.b, tx, "", args.PrivateFor, true) } // Sign calculates an ECDSA signature for: @@ -1673,14 +2178,30 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") } + // Quorum + // setDefaults calls DoEstimateGas in ethereum1.9.0, private transaction is not supported for that feature + // set gas to constant if nil + if args.IsPrivate() && args.Gas == nil { + gas := (hexutil.Uint64)(90000) + args.Gas = &gas + } + // /Quorum if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } + //TODO ricardolyn: evaluate if this change affects Quorum 0 gas price // Before actually sign the transaction, ensure the transaction fee is reasonable. if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { return nil, err } - tx, err := s.sign(args.From, args.toTransaction()) + // Quorum + toSign := args.toTransaction() + + if args.IsPrivate() { + toSign.SetPrivate() + } + // /Quorum + tx, err := s.sign(args.From, toSign) if err != nil { return nil, err } @@ -1707,7 +2228,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err transactions := make([]*RPCTransaction, 0, len(pending)) for _, tx := range pending { var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { + if tx.Protected() && !tx.IsPrivate() { signer = types.NewEIP155Signer(tx.ChainId()) } from, _ := types.Sender(signer, tx) @@ -1724,6 +2245,12 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if sendArgs.Nonce == nil { return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec") } + // setDefaults calls DoEstimateGas in ethereum1.9.0, private transaction is not supported for that feature + // set gas to constant if nil + if sendArgs.IsPrivate() && sendArgs.Gas == nil { + gas := (hexutil.Uint64)(90000) + sendArgs.Gas = &gas + } if err := sendArgs.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } @@ -1748,7 +2275,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr } for _, p := range pending { var signer types.Signer = types.HomesteadSigner{} - if p.Protected() { + if p.IsPrivate() { + signer = types.QuorumPrivateTxSigner{} + } else if p.Protected() { signer = types.NewEIP155Signer(p.ChainId()) } wantSigHash := signer.Hash(matchTx) @@ -1761,7 +2290,12 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction()) + newTx := sendArgs.toTransaction() + // set v param to 37 to indicate private tx before submitting to the signer. + if sendArgs.IsPrivate() { + newTx.SetPrivate() + } + signedTx, err := s.sign(sendArgs.From, newTx) if err != nil { return common.Hash{}, err } @@ -1937,3 +2471,303 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { } return nil } + +// Quorum +// Please note: This is a temporary integration to improve performance in high-latency +// environments when sending many private transactions. It will be removed at a later +// date when account management is handled outside Ethereum. + +type AsyncSendTxArgs struct { + SendTxArgs + CallbackUrl string `json:"callbackUrl"` +} + +type AsyncResultSuccess struct { + Id string `json:"id,omitempty"` + TxHash common.Hash `json:"txHash"` +} + +type AsyncResultFailure struct { + Id string `json:"id,omitempty"` + Error string `json:"error"` +} + +type Async struct { + sync.Mutex + sem chan struct{} +} + +func (s *PublicTransactionPoolAPI) send(ctx context.Context, asyncArgs AsyncSendTxArgs) { + + txHash, err := s.SendTransaction(ctx, asyncArgs.SendTxArgs) + + if asyncArgs.CallbackUrl != "" { + + //don't need to nil check this since id is required for every geth rpc call + //even though this is stated in the specification as an "optional" parameter + jsonId := ctx.Value("id").(*json.RawMessage) + id := string(*jsonId) + + var resultResponse interface{} + if err != nil { + resultResponse = &AsyncResultFailure{Id: id, Error: err.Error()} + } else { + resultResponse = &AsyncResultSuccess{Id: id, TxHash: txHash} + } + + buf := new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(resultResponse) + if err != nil { + log.Info("Error encoding callback JSON", "err", err.Error()) + return + } + _, err = http.Post(asyncArgs.CallbackUrl, "application/json", buf) + if err != nil { + log.Info("Error sending callback", "err", err.Error()) + return + } + } + +} + +func newAsync(n int) *Async { + a := &Async{ + sem: make(chan struct{}, n), + } + return a +} + +var async = newAsync(100) + +// SendTransactionAsync creates a transaction for the given argument, signs it, and +// submits it to the transaction pool. This call returns immediately to allow sending +// many private transactions/bursts of transactions without waiting for the recipient +// parties to confirm receipt of the encrypted payloads. An optional callbackUrl may +// be specified--when a transaction is submitted to the transaction pool, it will be +// called with a POST request containing either {"error": "error message"} or +// {"txHash": "0x..."}. +// +// Please note: This is a temporary integration to improve performance in high-latency +// environments when sending many private transactions. It will be removed at a later +// date when account management is handled outside Ethereum. +func (s *PublicTransactionPoolAPI) SendTransactionAsync(ctx context.Context, args AsyncSendTxArgs) (common.Hash, error) { + + select { + case async.sem <- struct{}{}: + go func() { + s.send(ctx, args) + <-async.sem + }() + return common.Hash{}, nil + default: + return common.Hash{}, errors.New("too many concurrent requests") + } +} + +// GetQuorumPayload returns the contents of a private transaction +func (s *PublicBlockChainAPI) GetQuorumPayload(digestHex string) (string, error) { + if !private.IsQuorumPrivacyEnabled() { + return "", fmt.Errorf("PrivateTransactionManager is not enabled") + } + if len(digestHex) < 3 { + return "", fmt.Errorf("Invalid digest hex") + } + if digestHex[:2] == "0x" { + digestHex = digestHex[2:] + } + b, err := hex.DecodeString(digestHex) + if err != nil { + return "", err + } + if len(b) != common.EncryptedPayloadHashLength { + return "", fmt.Errorf("Expected a Quorum digest of length 64, but got %d", len(b)) + } + _, _, data, _, err := private.P.Receive(common.BytesToEncryptedPayloadHash(b)) + if err != nil { + return "", err + } + return fmt.Sprintf("0x%x", data), nil +} + +func checkAndHandlePrivateTransaction(ctx context.Context, b Backend, tx *types.Transaction, privateTxArgs *PrivateTxArgs, from common.Address, txnType TransactionType) (isPrivate bool, hash common.EncryptedPayloadHash, err error) { + isPrivate = privateTxArgs != nil && privateTxArgs.PrivateFor != nil + if !isPrivate { + return + } + + if err = privateTxArgs.PrivacyFlag.Validate(); err != nil { + return + } + + if !b.ChainConfig().IsPrivacyEnhancementsEnabled(b.CurrentBlock().Number()) && privateTxArgs.PrivacyFlag.IsNotStandardPrivate() { + err = fmt.Errorf("PrivacyEnhancements are disabled. Can only accept transactions with PrivacyFlag=0(StandardPrivate).") + return + } + + if len(tx.Data()) > 0 { + // check private contract exists on the node initiating the transaction + if tx.To() != nil && privateTxArgs.PrivacyFlag.IsNotStandardPrivate() { + state, _, lerr := b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(b.CurrentBlock().Number().Uint64())) + if lerr != nil && state == nil { + err = fmt.Errorf("state not found") + return + } + if state.GetCode(*tx.To()) == nil { + err = fmt.Errorf("contract not found. cannot transact") + return + } + } + + hash, err = handlePrivateTransaction(ctx, b, tx, privateTxArgs, from, txnType) + + return + } + + return +} + +// If transaction is raw, the tx payload is indeed the hash of the encrypted payload +// +// For private transaction, run a simulated execution in order to +// 1. Find all affected private contract accounts then retrieve encrypted payload hashes of their creation txs +// 2. Calculate Merkle Root as the result of the simulated execution +// The above information along with private originating payload are sent to Transaction Manager +// to obtain hash of the encrypted private payload +func handlePrivateTransaction(ctx context.Context, b Backend, tx *types.Transaction, privateTxArgs *PrivateTxArgs, from common.Address, txnType TransactionType) (hash common.EncryptedPayloadHash, err error) { + defer func(start time.Time) { + log.Debug("Handle Private Transaction finished", "took", time.Since(start)) + }(time.Now()) + + data := tx.Data() + + var affectedCATxHashes common.EncryptedPayloadHashes // of affected contract accounts + var merkleRoot common.Hash + log.Debug("sending private tx", "txnType", txnType, "data", common.FormatTerminalString(data), "privatefrom", privateTxArgs.PrivateFrom, "privatefor", privateTxArgs.PrivateFor, "privacyFlag", privateTxArgs.PrivacyFlag) + + switch txnType { + case FillTransaction: + hash, err = private.P.StoreRaw(data, privateTxArgs.PrivateFrom) + return + case RawTransaction: + hash = common.BytesToEncryptedPayloadHash(data) + privatePayload, _, _, revErr := private.P.ReceiveRaw(hash) + if revErr != nil { + return common.EncryptedPayloadHash{}, revErr + } + log.Trace("received raw payload", "hash", hash, "privatepayload", common.FormatTerminalString(privatePayload)) + var privateTx *types.Transaction + if tx.To() == nil { + privateTx = types.NewContractCreation(tx.Nonce(), tx.Value(), tx.Gas(), tx.GasPrice(), privatePayload) + } else { + privateTx = types.NewTransaction(tx.Nonce(), *tx.To(), tx.Value(), tx.Gas(), tx.GasPrice(), privatePayload) + } + affectedCATxHashes, merkleRoot, err = simulateExecutionForPE(ctx, b, from, privateTx, privateTxArgs) + log.Trace("after simulation", "affectedCATxHashes", affectedCATxHashes, "merkleRoot", merkleRoot, "privacyFlag", privateTxArgs.PrivacyFlag, "error", err) + if err != nil { + return + } + + _, _, data, err = private.P.SendSignedTx(hash, privateTxArgs.PrivateFor, &engine.ExtraMetadata{ + ACHashes: affectedCATxHashes, + ACMerkleRoot: merkleRoot, + PrivacyFlag: privateTxArgs.PrivacyFlag, + }) + if err != nil { + return + } + + case NormalTransaction: + affectedCATxHashes, merkleRoot, err = simulateExecutionForPE(ctx, b, from, tx, privateTxArgs) + log.Trace("after simulation", "affectedCATxHashes", affectedCATxHashes, "merkleRoot", merkleRoot, "privacyFlag", privateTxArgs.PrivacyFlag, "error", err) + if err != nil { + return + } + + _, _, hash, err = private.P.Send(data, privateTxArgs.PrivateFrom, privateTxArgs.PrivateFor, &engine.ExtraMetadata{ + ACHashes: affectedCATxHashes, + ACMerkleRoot: merkleRoot, + PrivacyFlag: privateTxArgs.PrivacyFlag, + }) + if err != nil { + return + } + } + + log.Info("sent private signed tx", + "data", common.FormatTerminalString(data), + "hash", hash, + "privatefrom", privateTxArgs.PrivateFrom, + "privatefor", privateTxArgs.PrivateFor, + "affectedCATxHashes", affectedCATxHashes, + "merkleroot", merkleRoot, + "privacyflag", privateTxArgs.PrivacyFlag) + + return +} + +// simulateExecutionForPE simulates execution of a private transaction for enhanced privacy +// +// Returns hashes of encrypted payload of creation transactions for all affected contract accounts +// and the merkle root combining all affected contract accounts after the simulation +func simulateExecutionForPE(ctx context.Context, b Backend, from common.Address, privateTx *types.Transaction, privateTxArgs *PrivateTxArgs) (common.EncryptedPayloadHashes, common.Hash, error) { + // skip simulation if privacy enhancements are disabled + if !b.ChainConfig().IsPrivacyEnhancementsEnabled(b.CurrentBlock().Number()) { + return nil, common.Hash{}, nil + } + + evm, err := runSimulation(ctx, b, from, privateTx) + if evm == nil { + log.Debug("TX Simulation setup failed", "error", err) + return nil, common.Hash{}, err + } + if err != nil { + if privateTxArgs.PrivacyFlag.IsStandardPrivate() { + log.Debug("An error occurred during StandardPrivate transaction simulation. "+ + "Continuing to simulation checks.", "error", err) + } else { + log.Trace("Simulated execution", "error", err) + return nil, common.Hash{}, err + } + } + affectedContractsHashes := make(common.EncryptedPayloadHashes) + var merkleRoot common.Hash + addresses := evm.AffectedContracts() + privacyFlag := privateTxArgs.PrivacyFlag + log.Trace("after simulation run", "numberOfAffectedContracts", len(addresses), "privacyFlag", privacyFlag) + for _, addr := range addresses { + // GetPrivacyMetadata is invoked directly on the privateState (as the tx is private) and it returns: + // 1. public contacts: privacyMetadata = nil, err = nil + // 2. private contracts of type: + // 2.1. StandardPrivate: privacyMetadata = nil, err = "The provided contract does not have privacy metadata" + // 2.2. PartyProtection/PSV: privacyMetadata = , err = nil + privacyMetadata, err := evm.StateDB.GetPrivacyMetadata(addr) + log.Debug("Found affected contract", "address", addr.Hex(), "privacyMetadata", privacyMetadata) + //privacyMetadata not found=non-party, or another db error + if err != nil && privacyFlag.IsNotStandardPrivate() { + return nil, common.Hash{}, errors.New("PrivacyMetadata not found: " + err.Error()) + } + // when we run simulation, it's possible that affected contracts may contain public ones + // public contract will not have any privacyMetadata attached + // standard private will be nil + if privacyMetadata == nil { + continue + } + //if affecteds are not all the same return an error + if privacyFlag != privacyMetadata.PrivacyFlag { + return nil, common.Hash{}, errors.New("sent privacy flag doesn't match all affected contract flags") + } + + affectedContractsHashes.Add(privacyMetadata.CreationTxHash) + } + //only calculate the merkle root if all contracts are psv + if privacyFlag.Has(engine.PrivacyFlagStateValidation) { + merkleRoot, err = evm.CalculateMerkleRoot() + if err != nil { + return nil, common.Hash{}, err + } + } + log.Trace("post-execution run", "merkleRoot", merkleRoot, "affectedhashes", affectedContractsHashes) + return affectedContractsHashes, merkleRoot, nil +} + +//End-Quorum diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go new file mode 100644 index 0000000000..483cc0258d --- /dev/null +++ b/internal/ethapi/api_test.go @@ -0,0 +1,745 @@ +package ethapi + +import ( + "context" + "math/big" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/multitenancy" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/private" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/private/engine/notinuse" + "github.com/ethereum/go-ethereum/rpc" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + "github.com/stretchr/testify/assert" +) + +var ( + arbitraryCtx = context.Background() + arbitraryPrivateFrom = "arbitrary private from" + privateTxArgs = &PrivateTxArgs{ + PrivateFrom: arbitraryPrivateFrom, + PrivateFor: []string{"arbitrary party 1", "arbitrary party 2"}, + } + arbitraryFrom = common.BytesToAddress([]byte("arbitrary address")) + + arbitrarySimpleStorageContractEncryptedPayloadHash = common.BytesToEncryptedPayloadHash([]byte("arbitrary payload hash")) + + simpleStorageContractCreationTx = types.NewContractCreation( + 0, + big.NewInt(0), + hexutil.MustDecodeUint64("0x47b760"), + big.NewInt(0), + hexutil.MustDecode("0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029")) + + rawSimpleStorageContractCreationTx = types.NewContractCreation( + 0, + big.NewInt(0), + hexutil.MustDecodeUint64("0x47b760"), + big.NewInt(0), + arbitrarySimpleStorageContractEncryptedPayloadHash.Bytes()) + + arbitrarySimpleStorageContractAddress common.Address + arbitraryStandardPrivateSimpleStorageContractAddress common.Address + + simpleStorageContractMessageCallTx *types.Transaction + standardPrivateSimpleStorageContractMessageCallTx *types.Transaction + rawStandardPrivateSimpleStorageContractMessageCallTx *types.Transaction + + arbitraryCurrentBlockNumber = big.NewInt(1) + + publicStateDB *state.StateDB + privateStateDB *state.StateDB +) + +func TestMain(m *testing.M) { + setup() + retCode := m.Run() + teardown() + os.Exit(retCode) +} + +func setup() { + log.Root().SetHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(true))) + var err error + + memdb := rawdb.NewMemoryDatabase() + db := state.NewDatabase(memdb) + + publicStateDB, err = state.New(common.Hash{}, db, nil) + if err != nil { + panic(err) + } + privateStateDB, err = state.New(common.Hash{}, db, nil) + if err != nil { + panic(err) + } + + private.P = &StubPrivateTransactionManager{} + + key, _ := crypto.GenerateKey() + from := crypto.PubkeyToAddress(key.PublicKey) + + arbitrarySimpleStorageContractAddress = crypto.CreateAddress(from, 0) + + simpleStorageContractMessageCallTx = types.NewTransaction( + 0, + arbitrarySimpleStorageContractAddress, + big.NewInt(0), + hexutil.MustDecodeUint64("0x47b760"), + big.NewInt(0), + hexutil.MustDecode("0x60fe47b1000000000000000000000000000000000000000000000000000000000000000d")) + + arbitraryStandardPrivateSimpleStorageContractAddress = crypto.CreateAddress(from, 1) + + standardPrivateSimpleStorageContractMessageCallTx = types.NewTransaction( + 0, + arbitraryStandardPrivateSimpleStorageContractAddress, + big.NewInt(0), + hexutil.MustDecodeUint64("0x47b760"), + big.NewInt(0), + hexutil.MustDecode("0x60fe47b1000000000000000000000000000000000000000000000000000000000000000e")) + + rawStandardPrivateSimpleStorageContractMessageCallTx = types.NewTransaction( + 0, + arbitraryStandardPrivateSimpleStorageContractAddress, + big.NewInt(0), + hexutil.MustDecodeUint64("0x47b760"), + big.NewInt(0), + arbitrarySimpleStorageContractEncryptedPayloadHash.Bytes()) + +} + +func teardown() { + log.Root().SetHandler(log.DiscardHandler()) +} + +func TestSimulateExecution_whenStandardPrivateCreation(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractCreationTx, privateTxArgs) + + assert.NoError(err, "simulate execution") + assert.Empty(affectedCACreationTxHashes, "creation tx should not have any affected contract creation tx hashes") + assert.Equal(common.Hash{}, merkleRoot, "no private state validation") +} + +func TestSimulateExecution_whenPartyProtectionCreation(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractCreationTx, privateTxArgs) + + assert.NoError(err, "simulation execution") + assert.Empty(affectedCACreationTxHashes, "creation tx should not have any affected contract creation tx hashes") + assert.Equal(common.Hash{}, merkleRoot, "no private state validation") +} + +func TestSimulateExecution_whenCreationWithStateValidation(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStateValidation + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractCreationTx, privateTxArgs) + + assert.NoError(err, "simulate execution") + assert.Empty(affectedCACreationTxHashes, "creation tx should not have any affected contract creation tx hashes") + assert.NotEqual(common.Hash{}, merkleRoot, "private state validation") +} + +func TestSimulateExecution_whenStandardPrivateMessageCall(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + privateStateDB.SetCode(arbitraryStandardPrivateSimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000002")) + privateStateDB.SetState(arbitraryStandardPrivateSimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, standardPrivateSimpleStorageContractMessageCallTx, privateTxArgs) + + log.Debug("state", "state", privateStateDB.GetState(arbitraryStandardPrivateSimpleStorageContractAddress, common.Hash{0})) + + assert.NoError(err, "simulate execution") + assert.Empty(affectedCACreationTxHashes, "standard private contract should not have any affected contract creation tx hashes") + assert.Equal(common.Hash{}, merkleRoot, "no private state validation") +} + +func TestSimulateExecution_StandardPrivateMessageCallSucceedsWheContractNotAvailableLocally(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, standardPrivateSimpleStorageContractMessageCallTx, privateTxArgs) + + log.Debug("state", "state", privateStateDB.GetState(arbitraryStandardPrivateSimpleStorageContractAddress, common.Hash{0})) + + assert.NoError(err, "simulate execution") + assert.Empty(affectedCACreationTxHashes, "standard private contract should not have any affected contract creation tx hashes") + assert.Equal(common.Hash{}, merkleRoot, "no private state validation") +} + +func TestSimulateExecution_whenPartyProtectionMessageCall(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + privateStateDB.SetCode(arbitrarySimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000001")) + privateStateDB.SetPrivacyMetadata(arbitrarySimpleStorageContractAddress, &state.PrivacyMetadata{ + PrivacyFlag: privateTxArgs.PrivacyFlag, + CreationTxHash: arbitrarySimpleStorageContractEncryptedPayloadHash, + }) + + privateStateDB.SetState(arbitrarySimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + expectedCACreationTxHashes := []common.EncryptedPayloadHash{arbitrarySimpleStorageContractEncryptedPayloadHash} + + log.Debug("state", "state", privateStateDB.GetState(arbitrarySimpleStorageContractAddress, common.Hash{0})) + + assert.NoError(err, "simulate execution") + assert.NotEmpty(affectedCACreationTxHashes, "affected contract accounts' creation transacton hashes") + assert.Equal(common.Hash{}, merkleRoot, "no private state validation") + assert.True(len(affectedCACreationTxHashes) == len(expectedCACreationTxHashes)) +} + +func TestSimulateExecution_whenPartyProtectionMessageCallAndPrivacyEnhancementsDisabled(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + params.QuorumTestChainConfig.PrivacyEnhancementsBlock = nil + defer func() { params.QuorumTestChainConfig.PrivacyEnhancementsBlock = big.NewInt(0) }() + + stbBackend := &StubBackend{} + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, stbBackend, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + // the simulation returns early without executing the transaction + assert.False(stbBackend.getEVMCalled, "simulation is ended early - before getEVM is called") + assert.NoError(err, "simulate execution") + assert.Empty(affectedCACreationTxHashes, "affected contract accounts' creation transacton hashes") + assert.Equal(common.Hash{}, merkleRoot, "no private state validation") +} + +func TestSimulateExecution_whenStateValidationMessageCall(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStateValidation + + privateStateDB.SetCode(arbitrarySimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000001")) + privateStateDB.SetPrivacyMetadata(arbitrarySimpleStorageContractAddress, &state.PrivacyMetadata{ + PrivacyFlag: privateTxArgs.PrivacyFlag, + CreationTxHash: arbitrarySimpleStorageContractEncryptedPayloadHash, + }) + + privateStateDB.SetState(arbitrarySimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + affectedCACreationTxHashes, merkleRoot, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + expectedCACreationTxHashes := []common.EncryptedPayloadHash{arbitrarySimpleStorageContractEncryptedPayloadHash} + + log.Debug("state", "state", privateStateDB.GetState(arbitrarySimpleStorageContractAddress, common.Hash{0})) + + assert.NoError(err, "simulate execution") + assert.NotEmpty(affectedCACreationTxHashes, "affected contract accounts' creation transacton hashes") + assert.NotEqual(common.Hash{}, merkleRoot, "private state validation") + assert.True(len(affectedCACreationTxHashes) == len(expectedCACreationTxHashes)) +} + +//mix and match flags +func TestSimulateExecution_PrivacyFlagPartyProtectionCallingStandardPrivateContract_Error(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + privateStateDB.SetCode(arbitraryStandardPrivateSimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000002")) + privateStateDB.SetState(arbitraryStandardPrivateSimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + _, _, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, standardPrivateSimpleStorageContractMessageCallTx, privateTxArgs) + + log.Debug("state", "state", privateStateDB.GetState(arbitraryStandardPrivateSimpleStorageContractAddress, common.Hash{0})) + + assert.Error(err, "simulate execution") +} + +func TestSimulateExecution_StandardPrivateFlagCallingPartyProtectionContract_Error(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + privateStateDB.SetCode(arbitrarySimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000001")) + privateStateDB.SetPrivacyMetadata(arbitrarySimpleStorageContractAddress, &state.PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + CreationTxHash: arbitrarySimpleStorageContractEncryptedPayloadHash, + }) + + privateStateDB.SetState(arbitrarySimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + _, _, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + assert.Error(err, "simulate execution") +} + +func TestSimulateExecution_StandardPrivateFlagCallingStateValidationContract_Error(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + privateStateDB.SetCode(arbitrarySimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000001")) + privateStateDB.SetPrivacyMetadata(arbitrarySimpleStorageContractAddress, &state.PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStateValidation, + CreationTxHash: arbitrarySimpleStorageContractEncryptedPayloadHash, + }) + + privateStateDB.SetState(arbitrarySimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + _, _, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + log.Debug("state", "state", privateStateDB.GetState(arbitrarySimpleStorageContractAddress, common.Hash{0})) + + assert.Error(err, "simulate execution") +} + +func TestSimulateExecution_PartyProtectionFlagCallingStateValidationContract_Error(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + privateStateDB.SetCode(arbitrarySimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000001")) + privateStateDB.SetPrivacyMetadata(arbitrarySimpleStorageContractAddress, &state.PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagStateValidation, + CreationTxHash: arbitrarySimpleStorageContractEncryptedPayloadHash, + }) + + privateStateDB.SetState(arbitrarySimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + _, _, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + log.Debug("state", "state", privateStateDB.GetState(arbitrarySimpleStorageContractAddress, common.Hash{0})) + + assert.Error(err, "simulate execution") +} + +func TestSimulateExecution_StateValidationFlagCallingPartyProtectionContract_Error(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStateValidation + + privateStateDB.SetCode(arbitrarySimpleStorageContractAddress, hexutil.MustDecode("0x608060405234801561001057600080fd5b506040516020806101618339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610109806100586000396000f3fe6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146099575b600080fd5b348015605957600080fd5b50608360048036036020811015606e57600080fd5b810190808035906020019092919050505060c1565b6040518082815260200191505060405180910390f35b34801560a457600080fd5b5060ab60d4565b6040518082815260200191505060405180910390f35b6000816000819055506000549050919050565b6000805490509056fea165627a7a723058203624ca2e3479d3fa5a12d97cf3dae0d9a6de3a3b8a53c8605b9cd398d9766b9f00290000000000000000000000000000000000000000000000000000000000000001")) + privateStateDB.SetPrivacyMetadata(arbitrarySimpleStorageContractAddress, &state.PrivacyMetadata{ + PrivacyFlag: engine.PrivacyFlagPartyProtection, + CreationTxHash: arbitrarySimpleStorageContractEncryptedPayloadHash, + }) + + privateStateDB.SetState(arbitrarySimpleStorageContractAddress, common.Hash{0}, common.Hash{100}) + privateStateDB.Commit(true) + + _, _, err := simulateExecutionForPE(arbitraryCtx, &StubBackend{}, arbitraryFrom, simpleStorageContractMessageCallTx, privateTxArgs) + + //expectedCACreationTxHashes := []common.EncryptedPayloadHash{arbitrarySimpleStorageContractEncryptedPayloadHash} + + log.Debug("state", "state", privateStateDB.GetState(arbitrarySimpleStorageContractAddress, common.Hash{0})) + + assert.Error(err, "simulate execution") +} + +func TestHandlePrivateTransaction_whenInvalidFlag(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = 4 + + _, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, NormalTransaction) + + assert.Error(err, "invalid privacyFlag") +} + +func TestHandlePrivateTransaction_withPartyProtectionTxAndPrivacyEnhancementsIsDisabled(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = 1 + params.QuorumTestChainConfig.PrivacyEnhancementsBlock = nil + defer func() { params.QuorumTestChainConfig.PrivacyEnhancementsBlock = big.NewInt(0) }() + + _, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, NormalTransaction) + + assert.Error(err, "PrivacyEnhancements are disabled. Can only accept transactions with PrivacyFlag=0(StandardPrivate).") +} + +func TestHandlePrivateTransaction_whenStandardPrivateCreation(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + isPrivate, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, NormalTransaction) + + if err != nil { + t.Fatalf("%s", err) + } + + assert.True(isPrivate, "must be a private transaction") +} + +func TestHandlePrivateTransaction_whenStandardPrivateCallingContractThatIsNotAvailableLocally(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + isPrivate, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, standardPrivateSimpleStorageContractMessageCallTx, privateTxArgs, arbitraryFrom, NormalTransaction) + + assert.NoError(err, "no error expected") + + assert.True(isPrivate, "must be a private transaction") +} + +func TestHandlePrivateTransaction_whenPartyProtectionCallingContractThatIsNotAvailableLocally(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + isPrivate, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, standardPrivateSimpleStorageContractMessageCallTx, privateTxArgs, arbitraryFrom, NormalTransaction) + + assert.Error(err, "handle invalid message call") + + assert.True(isPrivate, "must be a private transaction") +} + +func TestHandlePrivateTransaction_whenPartyProtectionCallingStandardPrivate(t *testing.T) { + assert := assert.New(t) + privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection + + isPrivate, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, standardPrivateSimpleStorageContractMessageCallTx, privateTxArgs, arbitraryFrom, NormalTransaction) + + assert.Error(err, "handle invalid message call") + + assert.True(isPrivate, "must be a private transaction") +} + +func TestHandlePrivateTransaction_whenRawStandardPrivateCreation(t *testing.T) { + assert := assert.New(t) + private.P = &StubPrivateTransactionManager{creation: true} + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + isPrivate, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, rawSimpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, RawTransaction) + + assert.NoError(err, "raw standard private creation succeeded") + assert.True(isPrivate, "must be a private transaction") +} + +func TestHandlePrivateTransaction_whenRawStandardPrivateMessageCall(t *testing.T) { + assert := assert.New(t) + private.P = &StubPrivateTransactionManager{creation: false} + privateTxArgs.PrivacyFlag = engine.PrivacyFlagStandardPrivate + + _, err := handlePrivateTransaction(arbitraryCtx, &StubBackend{}, rawStandardPrivateSimpleStorageContractMessageCallTx, privateTxArgs, arbitraryFrom, RawTransaction) + + assert.NoError(err, "raw standard private msg call succeeded") + +} + +type StubBackend struct { + getEVMCalled bool + mockAccountExtraDataStateGetter *vm.MockAccountExtraDataStateGetter +} + +func (sb *StubBackend) SupportsMultitenancy(rpcCtx context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) { + panic("implement me") +} + +func (sb *StubBackend) AccountExtraDataStateGetterByNumber(context.Context, rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) { + return sb.mockAccountExtraDataStateGetter, nil +} + +func (sb *StubBackend) IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*multitenancy.ContractSecurityAttribute) (bool, error) { + panic("implement me") +} + +func (sb *StubBackend) GetEVM(ctx context.Context, msg core.Message, state vm.MinimalApiState, header *types.Header) (*vm.EVM, func() error, error) { + sb.getEVMCalled = true + vmCtx := core.NewEVMContext(msg, &types.Header{ + Coinbase: arbitraryFrom, + Number: arbitraryCurrentBlockNumber, + Time: 0, + Difficulty: big.NewInt(0), + GasLimit: 0, + }, nil, &arbitraryFrom) + return vm.NewEVM(vmCtx, publicStateDB, privateStateDB, params.QuorumTestChainConfig, vm.Config{}), nil, nil +} + +func (sb *StubBackend) CurrentBlock() *types.Block { + return types.NewBlock(&types.Header{ + Number: arbitraryCurrentBlockNumber, + }, nil, nil, nil) +} + +func (sb *StubBackend) Downloader() *downloader.Downloader { + panic("implement me") +} + +func (sb *StubBackend) ProtocolVersion() int { + panic("implement me") +} + +func (sb *StubBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { + panic("implement me") +} + +func (sb *StubBackend) ChainDb() ethdb.Database { + panic("implement me") +} + +func (sb *StubBackend) EventMux() *event.TypeMux { + panic("implement me") +} + +func (sb *StubBackend) AccountManager() *accounts.Manager { + panic("implement me") +} + +func (sb *StubBackend) ExtRPCEnabled() bool { + panic("implement me") +} + +func (sb *StubBackend) CallTimeOut() time.Duration { + panic("implement me") +} + +func (sb *StubBackend) RPCTxFeeCap() float64 { + panic("implement me") +} + +func (sb *StubBackend) RPCGasCap() uint64 { + panic("implement me") +} + +func (sb *StubBackend) SetHead(number uint64) { + panic("implement me") +} + +func (sb *StubBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { + panic("implement me") +} + +func (sb *StubBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + panic("implement me") +} + +func (sb *StubBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { + panic("implement me") +} + +func (sb *StubBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) { + panic("implement me") +} + +func (sb *StubBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + panic("implement me") +} + +func (sb *StubBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { + panic("implement me") +} + +func (sb *StubBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (vm.MinimalApiState, *types.Header, error) { + return &StubMinimalApiState{}, nil, nil +} + +func (sb *StubBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.MinimalApiState, *types.Header, error) { + return &StubMinimalApiState{}, nil, nil +} + +func (sb *StubBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { + panic("implement me") +} + +func (sb *StubBackend) GetTd(blockHash common.Hash) *big.Int { + panic("implement me") +} + +func (sb *StubBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + panic("implement me") +} + +func (sb *StubBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + panic("implement me") +} + +func (sb *StubBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { + panic("implement me") +} + +func (sb *StubBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + panic("implement me") +} + +func (sb *StubBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { + panic("implement me") +} + +func (sb *StubBackend) GetPoolTransactions() (types.Transactions, error) { + panic("implement me") +} + +func (sb *StubBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { + panic("implement me") +} + +func (sb *StubBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { + panic("implement me") +} + +func (sb *StubBackend) Stats() (pending int, queued int) { + panic("implement me") +} + +func (sb *StubBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { + panic("implement me") +} + +func (sb *StubBackend) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription { + panic("implement me") +} + +func (sb *StubBackend) BloomStatus() (uint64, uint64) { + panic("implement me") +} + +func (sb *StubBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) { + panic("implement me") +} + +func (sb *StubBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { + panic("implement me") +} + +func (sb *StubBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + panic("implement me") +} + +func (sb *StubBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + panic("implement me") +} + +func (sb *StubBackend) ChainConfig() *params.ChainConfig { + return params.QuorumTestChainConfig +} + +func (sb *StubBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { + panic("implement me") +} + +type StubMinimalApiState struct { +} + +func (StubMinimalApiState) GetBalance(addr common.Address) *big.Int { + panic("implement me") +} + +func (StubMinimalApiState) SetBalance(addr common.Address, balance *big.Int) { + panic("implement me") +} + +func (StubMinimalApiState) GetCode(addr common.Address) []byte { + return nil +} + +func (StubMinimalApiState) GetState(a common.Address, b common.Hash) common.Hash { + panic("implement me") +} + +func (StubMinimalApiState) GetNonce(addr common.Address) uint64 { + panic("implement me") +} + +func (StubMinimalApiState) SetNonce(addr common.Address, nonce uint64) { + panic("implement me") +} + +func (StubMinimalApiState) SetCode(common.Address, []byte) { + panic("implement me") +} + +func (StubMinimalApiState) GetPrivacyMetadata(addr common.Address) (*state.PrivacyMetadata, error) { + panic("implement me") +} + +func (StubMinimalApiState) GetManagedParties(addr common.Address) ([]string, error) { + panic("implement me") +} + +func (StubMinimalApiState) GetRLPEncodedStateObject(addr common.Address) ([]byte, error) { + panic("implement me") +} + +func (StubMinimalApiState) GetProof(common.Address) ([][]byte, error) { + panic("implement me") +} + +func (StubMinimalApiState) GetStorageProof(common.Address, common.Hash) ([][]byte, error) { + panic("implement me") +} + +func (StubMinimalApiState) StorageTrie(addr common.Address) state.Trie { + panic("implement me") +} + +func (StubMinimalApiState) Error() error { + panic("implement me") +} + +func (StubMinimalApiState) GetCodeHash(common.Address) common.Hash { + panic("implement me") +} + +func (StubMinimalApiState) SetState(common.Address, common.Hash, common.Hash) { + panic("implement me") +} + +func (StubMinimalApiState) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { + panic("implement me") +} + +type StubPrivateTransactionManager struct { + notinuse.PrivateTransactionManager + creation bool +} + +func (sptm *StubPrivateTransactionManager) Send(data []byte, from string, to []string, extra *engine.ExtraMetadata) (string, []string, common.EncryptedPayloadHash, error) { + return "", nil, arbitrarySimpleStorageContractEncryptedPayloadHash, nil +} + +func (sptm *StubPrivateTransactionManager) EncryptPayload(data []byte, from string, to []string, extra *engine.ExtraMetadata) ([]byte, error) { + return nil, engine.ErrPrivateTxManagerNotSupported +} + +func (sptm *StubPrivateTransactionManager) DecryptPayload(payload common.DecryptRequest) ([]byte, *engine.ExtraMetadata, error) { + return nil, nil, engine.ErrPrivateTxManagerNotSupported +} + +func (sptm *StubPrivateTransactionManager) StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) { + return arbitrarySimpleStorageContractEncryptedPayloadHash, nil +} + +func (sptm *StubPrivateTransactionManager) SendSignedTx(data common.EncryptedPayloadHash, to []string, extra *engine.ExtraMetadata) (string, []string, []byte, error) { + return "", nil, arbitrarySimpleStorageContractEncryptedPayloadHash.Bytes(), nil +} + +func (sptm *StubPrivateTransactionManager) ReceiveRaw(data common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) { + if sptm.creation { + return hexutil.MustDecode("0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029"), "", nil, nil + } else { + return hexutil.MustDecode("0x60fe47b1000000000000000000000000000000000000000000000000000000000000000e"), "", nil, nil + } +} + +func (sptm *StubPrivateTransactionManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return true +} diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 074cd794a6..f1887e50ef 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -20,17 +20,18 @@ package ethapi import ( "context" "math/big" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "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" ) @@ -38,6 +39,7 @@ import ( // Backend interface provides the common API services (that are provided by // both full and light clients) with access to necessary functions. type Backend interface { + multitenancy.AuthorizationProvider // General Ethereum API Downloader() *downloader.Downloader ProtocolVersion() int @@ -45,6 +47,7 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool + CallTimeOut() time.Duration RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection @@ -56,11 +59,11 @@ type Backend interface { BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) - StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) - StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) + StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (vm.MinimalApiState, *types.Header, error) + StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.MinimalApiState, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetTd(ctx context.Context, hash common.Hash) *big.Int - GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) + GetEVM(ctx context.Context, msg core.Message, state vm.MinimalApiState, header *types.Header) (*vm.EVM, func() error, error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription @@ -85,6 +88,10 @@ type Backend interface { ChainConfig() *params.ChainConfig CurrentBlock() *types.Block + + // Quorum + // AccountExtraDataStateGetterByNumber returns state getter at a given block height + AccountExtraDataStateGetterByNumber(ctx context.Context, number rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/internal/plugin/protocol.go b/internal/plugin/protocol.go new file mode 100644 index 0000000000..465c59e84b --- /dev/null +++ b/internal/plugin/protocol.go @@ -0,0 +1,21 @@ +package plugin + +import ( + "errors" + + "github.com/hashicorp/go-plugin" +) + +const ( + DefaultProtocolVersion = 1 +) + +var ( + DefaultHandshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: DefaultProtocolVersion, + MagicCookieKey: "QUORUM_PLUGIN_MAGIC_COOKIE", + MagicCookieValue: "CB9F51969613126D93468868990F77A8470EB9177503C5A38D437FEFF7786E0941152E05C06A9A3313391059132A7F9CED86C0783FE63A8B38F01623C8257664", + } + + ErrNotSupported = errors.New("not supported") +) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 80ac92fe4a..9c6e1fcd30 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -18,22 +18,27 @@ package web3ext var Modules = map[string]string{ - "accounting": AccountingJs, - "admin": AdminJs, - "chequebook": ChequebookJs, - "clique": CliqueJs, - "ethash": EthashJs, - "debug": DebugJs, - "eth": EthJs, - "miner": MinerJs, - "net": NetJs, - "personal": PersonalJs, - "rpc": RpcJs, - "shh": ShhJs, - "swarmfs": SwarmfsJs, - "txpool": TxpoolJs, - "les": LESJs, - "lespay": LESPayJs, + "accounting": AccountingJs, + "admin": AdminJs, + "chequebook": ChequebookJs, + "clique": CliqueJs, + "ethash": EthashJs, + "debug": DebugJs, + "eth": EthJs, + "miner": MinerJs, + "net": NetJs, + "personal": PersonalJs, + "rpc": RpcJs, + "shh": ShhJs, + "swarmfs": SwarmfsJs, + "txpool": TxpoolJs, + "les": LESJs, + "lespay": LESPayJs, + "raft": Raft_JS, + "istanbul": Istanbul_JS, + "quorumPermission": QUORUM_NODE_JS, + "quorumExtension": Extension_JS, + "plugin_account": Account_Plugin_Js, } const ChequebookJs = ` @@ -106,7 +111,8 @@ web3._extend({ new web3._extend.Method({ name: 'status', call: 'clique_status', - params: 0 + params: 2, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter] }), ], properties: [ @@ -150,6 +156,11 @@ const AdminJs = ` web3._extend({ property: 'admin', methods: [ + new web3._extend.Method({ + name: 'reloadPlugin', + call: 'admin_reloadPlugin', + params: 1 + }), new web3._extend.Method({ name: 'addPeer', call: 'admin_addPeer', @@ -263,7 +274,14 @@ web3._extend({ new web3._extend.Method({ name: 'dumpBlock', call: 'debug_dumpBlock', - params: 1 + params: 2, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, ""] + }), + new web3._extend.Method({ + name: 'dumpAddress', + call: 'debug_dumpAddress', + params: 2, + inputFormatter: [web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'chaindbProperty', @@ -471,6 +489,17 @@ const EthJs = ` web3._extend({ property: 'eth', methods: [ + new web3._extend.Method({ + name: 'sendRawPrivateTransaction', + call: 'eth_sendRawPrivateTransaction', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'getContractPrivacyMetadata', + call: 'eth_getContractPrivacyMetadata', + params: 1 + }), new web3._extend.Method({ name: 'chainId', call: 'eth_chainId', @@ -545,6 +574,26 @@ web3._extend({ params: 3, inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter] }), + new web3._extend.Method({ + name: 'storageRoot', + call: 'eth_storageRoot', + params: 2, + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null] + }), + // QUORUM + new web3._extend.Method({ + name: 'sendTransactionAsync', + call: 'eth_sendTransactionAsync', + params: 1, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'getQuorumPayload', + call: 'eth_getQuorumPayload', + params: 1, + inputFormatter: [null] + }), + // END-QUORUM ], properties: [ new web3._extend.Property({ @@ -761,6 +810,282 @@ web3._extend({ }); ` +const Raft_JS = ` +web3._extend({ + property: 'raft', + methods: + [ + ], + properties: + [ + new web3._extend.Property({ + name: 'role', + getter: 'raft_role' + }), + new web3._extend.Method({ + name: 'addPeer', + call: 'raft_addPeer', + params: 1 + }), + new web3._extend.Method({ + name: 'addLearner', + call: 'raft_addLearner', + params: 1 + }), + new web3._extend.Method({ + name: 'promoteToPeer', + call: 'raft_promoteToPeer', + params: 1 + }), + new web3._extend.Method({ + name: 'removePeer', + call: 'raft_removePeer', + params: 1 + }), + new web3._extend.Property({ + name: 'leader', + getter: 'raft_leader' + }), + new web3._extend.Property({ + name: 'cluster', + getter: 'raft_cluster' + }), + ] +}) +` + +const QUORUM_NODE_JS = ` +web3._extend({ + property: 'quorumPermission', + methods: + [ + new web3._extend.Method({ + name: 'addOrg', + call: 'quorumPermission_addOrg', + params: 4, + inputFormatter: [null,null,web3._extend.formatters.inputAddressFormatter,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'approveOrg', + call: 'quorumPermission_approveOrg', + params: 4, + inputFormatter: [null,null,web3._extend.formatters.inputAddressFormatter,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'addSubOrg', + call: 'quorumPermission_addSubOrg', + params: 4, + inputFormatter: [null,null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'updateOrgStatus', + call: 'quorumPermission_updateOrgStatus', + params: 3, + inputFormatter: [null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'approveOrgStatus', + call: 'quorumPermission_approveOrgStatus', + params: 3, + inputFormatter: [null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'addNode', + call: 'quorumPermission_addNode', + params: 3, + inputFormatter: [null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'updateNodeStatus', + call: 'quorumPermission_updateNodeStatus', + params: 4, + inputFormatter: [null,null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'assignAdminRole', + call: 'quorumPermission_assignAdminRole', + params: 4, + inputFormatter: [null,web3._extend.formatters.inputAddressFormatter,null, web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'approveAdminRole', + call: 'quorumPermission_approveAdminRole', + params: 3, + inputFormatter: [null, web3._extend.formatters.inputAddressFormatter,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'addNewRole', + call: 'quorumPermission_addNewRole', + params: 6, + inputFormatter: [null,null,null,null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'removeRole', + call: 'quorumPermission_removeRole', + params: 3, + inputFormatter: [null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'addAccountToOrg', + call: 'quorumPermission_addAccountToOrg', + params: 4, + inputFormatter: [web3._extend.formatters.inputAddressFormatter,null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'changeAccountRole', + call: 'quorumPermission_changeAccountRole', + params: 4, + inputFormatter: [web3._extend.formatters.inputAddressFormatter,null,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'updateAccountStatus', + call: 'quorumPermission_updateAccountStatus', + params: 4, + inputFormatter: [null, web3._extend.formatters.inputAddressFormatter,null,web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'recoverBlackListedNode', + call: 'quorumPermission_recoverBlackListedNode', + params: 3, + inputFormatter: [null, null, web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'approveBlackListedNodeRecovery', + call: 'quorumPermission_approveBlackListedNodeRecovery', + params: 3, + inputFormatter: [null, null, web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'recoverBlackListedAccount', + call: 'quorumPermission_recoverBlackListedAccount', + params: 3, + inputFormatter: [null, web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'approveBlackListedAccountRecovery', + call: 'quorumPermission_approveBlackListedAccountRecovery', + params: 3, + inputFormatter: [null, web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'getOrgDetails', + call: 'quorumPermission_getOrgDetails', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'transactionAllowed', + call: 'quorumPermission_transactionAllowed', + params: 1, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'connectionAllowed', + call: 'quorumPermission_connectionAllowed', + params: 4, + inputFormatter: [null, null, null, null] + }), + + ], + properties: + [ + new web3._extend.Property({ + name: 'orgList', + getter: 'quorumPermission_orgList' + }), + new web3._extend.Property({ + name: 'nodeList', + getter: 'quorumPermission_nodeList' + }), + new web3._extend.Property({ + name: 'roleList', + getter: 'quorumPermission_roleList' + }), + new web3._extend.Property({ + name: 'acctList', + getter: 'quorumPermission_acctList' + }), + ] +}) +` + +const Istanbul_JS = ` +web3._extend({ + property: 'istanbul', + methods: + [ + new web3._extend.Method({ + name: 'getSnapshot', + call: 'istanbul_getSnapshot', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'getSnapshotAtHash', + call: 'istanbul_getSnapshotAtHash', + params: 1 + }), + new web3._extend.Method({ + name: 'getValidators', + call: 'istanbul_getValidators', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'getValidatorsAtHash', + call: 'istanbul_getValidatorsAtHash', + params: 1 + }), + new web3._extend.Method({ + name: 'propose', + call: 'istanbul_propose', + params: 2 + }), + new web3._extend.Method({ + name: 'discard', + call: 'istanbul_discard', + params: 1 + }), + + new web3._extend.Method({ + name: 'getSignersFromBlock', + call: 'istanbul_getSignersFromBlock', + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + }), + new web3._extend.Method({ + name: 'getSignersFromBlockByHash', + call: 'istanbul_getSignersFromBlockByHash', + params: 1 + }), + new web3._extend.Method({ + name: 'status', + call: 'istanbul_status', + params: 2, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter] + }), + new web3._extend.Method({ + name: 'isValidator', + call: 'istanbul_isValidator', + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + }), + + ], + properties: + [ + new web3._extend.Property({ + name: 'candidates', + getter: 'istanbul_candidates' + }), + new web3._extend.Property({ + name: 'nodeAddress', + getter: 'istanbul_nodeAddress' + }), + ] +}); +` + const AccountingJs = ` web3._extend({ property: 'accounting', @@ -815,46 +1140,77 @@ web3._extend({ call: 'les_getCheckpoint', params: 1 }), - new web3._extend.Method({ - name: 'clientInfo', - call: 'les_clientInfo', - params: 1 + ], + properties: + [ + new web3._extend.Property({ + name: 'latestCheckpoint', + getter: 'les_latestCheckpoint' + }), + new web3._extend.Property({ + name: 'checkpointContractAddress', + getter: 'les_getCheckpointContractAddress' }), + ] +}); +` + +const Extension_JS = ` +web3._extend({ + property: 'quorumExtension', + methods: + [ new web3._extend.Method({ - name: 'priorityClientInfo', - call: 'les_priorityClientInfo', - params: 3 + name: 'approveExtension', + call: 'quorumExtension_approveExtension', + params: 3, + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputTransactionFormatter] }), new web3._extend.Method({ - name: 'setClientParams', - call: 'les_setClientParams', - params: 2 + name: 'extendContract', + call: 'quorumExtension_extendContract', + params: 4, + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputTransactionFormatter] }), new web3._extend.Method({ - name: 'setDefaultParams', - call: 'les_setDefaultParams', - params: 1 + name: 'cancelExtension', + call: 'quorumExtension_cancelExtension', + params: 2, + inputFormatter: [web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputTransactionFormatter] }), new web3._extend.Method({ - name: 'addBalance', - call: 'les_addBalance', - params: 3 + name: 'getExtensionStatus', + call: 'quorumExtension_getExtensionStatus', + params: 1, + inputFormatter: [web3._extend.formatters.inputAddressFormatter] }), + ], properties: [ new web3._extend.Property({ - name: 'latestCheckpoint', - getter: 'les_latestCheckpoint' - }), - new web3._extend.Property({ - name: 'checkpointContractAddress', - getter: 'les_getCheckpointContractAddress' - }), - new web3._extend.Property({ - name: 'serverInfo', - getter: 'les_serverInfo' + name: 'activeExtensionContracts', + getter: 'quorumExtension_activeExtensionContracts' + }) + ] +}); +` + +const Account_Plugin_Js = ` +web3._extend({ + property: 'plugin_account', + methods: + [ + new web3._extend.Method({ + name: 'newAccount', + call: 'plugin@account_newAccount', + params: 1 }), + new web3._extend.Method({ + name: 'importRawKey', + call: 'plugin@account_importRawKey', + params: 2 + }) ] }); ` diff --git a/les/api_backend.go b/les/api_backend.go index 448260a198..1dd59ac198 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -20,6 +20,7 @@ import ( "context" "errors" "math/big" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -34,8 +35,10 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" + "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" ) type LesApiBackend struct { @@ -120,7 +123,7 @@ func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r return nil, errors.New("invalid arguments; neither block nor hash specified") } -func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { +func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (vm.MinimalApiState, *types.Header, error) { header, err := b.HeaderByNumber(ctx, number) if err != nil { return nil, nil, err @@ -131,7 +134,7 @@ func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B return light.NewState(ctx, header, b.eth.odr), header, nil } -func (b *LesApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { +func (b *LesApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.MinimalApiState, *types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) } @@ -169,9 +172,10 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { +func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, apiState vm.MinimalApiState, header *types.Header) (*vm.EVM, func() error, error) { + statedb := apiState.(*state.StateDB) context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) - return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil + return vm.NewEVM(context, statedb, statedb, b.eth.chainConfig, vm.Config{}), statedb.Error, nil } func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { @@ -261,6 +265,13 @@ func (b *LesApiBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +// Quorum +func (b *LesApiBackend) CallTimeOut() time.Duration { + return b.eth.config.EVMCallTimeOut +} + +// End Quorum + func (b *LesApiBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } @@ -282,3 +293,27 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +// Quorum +func (b *LesApiBackend) SupportsMultitenancy(rpcCtx context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) { + authToken, isPreauthenticated := rpcCtx.Value(rpc.CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken) + if isPreauthenticated && b.eth.config.EnableMultitenancy { + return authToken, true + } + return nil, false +} + +func (b *LesApiBackend) AccountExtraDataStateGetterByNumber(ctx context.Context, number rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) { + s, _, err := b.StateAndHeaderByNumber(ctx, number) + return s, err +} + +func (b *LesApiBackend) IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*multitenancy.ContractSecurityAttribute) (bool, error) { + auth, err := b.eth.contractAuthzProvider.IsAuthorized(ctx, authToken, attributes...) + if err != nil { + return false, err + } + return auth, nil +} + +// End Quorum diff --git a/les/client.go b/les/client.go index 49b21c9673..6f695b9f03 100644 --- a/les/client.go +++ b/les/client.go @@ -41,6 +41,7 @@ import ( lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -72,6 +73,17 @@ type LightEthereum struct { engine consensus.Engine accountManager *accounts.Manager netRPCService *ethapi.PublicNetAPI + + // Quorum - Multitenancy + // contractAuthzProvider is set after node starts instead in New() + contractAuthzProvider multitenancy.ContractAuthorizationProvider +} + +// Quorum +// +// Set the decision manager for multitenancy support +func (s *LightEthereum) SetContractAuthorizationManager(dm multitenancy.ContractAuthorizationProvider) { + s.contractAuthzProvider = dm } func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { @@ -103,7 +115,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { eventMux: ctx.EventMux, reqDist: newRequestDistributor(peers, &mclock.System{}), accountManager: ctx.AccountManager, - engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), + engine: eth.CreateConsensusEngine(ctx, chainConfig, config, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), @@ -130,9 +142,13 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { if checkpoint == nil { checkpoint = params.TrustedCheckpoints[genesisHash] } + newChainFunc := light.NewLightChain + if config.EnableMultitenancy { + newChainFunc = light.NewMultitenantLightChain + } // Note: NewLightChain adds the trusted checkpoint so it needs an ODR with // indexers already set but not started yet - if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint); err != nil { + if leth.blockchain, err = newChainFunc(leth.odr, leth.chainConfig, leth.engine, checkpoint); err != nil { return nil, err } leth.chainReader = leth.blockchain diff --git a/les/odr_test.go b/les/odr_test.go index d30642c4f7..6677dd920c 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -131,7 +131,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} context := core.NewEVMContext(msg, header, bc, nil) - vmenv := vm.NewEVM(context, statedb, config, vm.Config{}) + vmenv := vm.NewEVM(context, statedb, statedb, config, vm.Config{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) @@ -144,7 +144,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai state.SetBalance(bankAddr, math.MaxBig256) msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} context := core.NewEVMContext(msg, header, lc, nil) - vmenv := vm.NewEVM(context, state, config, vm.Config{}) + vmenv := vm.NewEVM(context, state, state, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp) if state.Error() == nil { diff --git a/les/server_handler.go b/les/server_handler.go index dd1c37f66d..5e97e4f3d9 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -478,7 +478,8 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { atomic.AddUint32(&p.invalidCount, 1) continue } - triedb := h.blockchain.StateCache().TrieDB() + statedb, _ := h.blockchain.StateCache() + triedb := statedb.TrieDB() account, err := h.getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) if err != nil { @@ -624,7 +625,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { continue } // Open the account or storage trie for the request - statedb := h.blockchain.StateCache() + statedb, _ := h.blockchain.StateCache() switch len(request.AccKey) { case 0: diff --git a/les/test_helper.go b/les/test_helper.go index 28906f1f10..9821d69910 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -115,43 +115,43 @@ func prepare(n int, backend *backends.SimulatedBackend) { // bankUser transfers some ether to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey) - backend.SendTransaction(ctx, tx) + backend.SendTransaction(ctx, tx, bind.PrivateTxArgs{}) case 1: bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) // bankUser transfers more ether to user1 tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, nil, nil), signer, bankKey) - backend.SendTransaction(ctx, tx1) + backend.SendTransaction(ctx, tx1, bind.PrivateTxArgs{}) // user1 relays ether to user2 tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, nil, nil), signer, userKey1) - backend.SendTransaction(ctx, tx2) + backend.SendTransaction(ctx, tx2, bind.PrivateTxArgs{}) // user1 deploys a test contract tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, userKey1) - backend.SendTransaction(ctx, tx3) + backend.SendTransaction(ctx, tx3, bind.PrivateTxArgs{}) testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1) // user1 deploys a event contract tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1) - backend.SendTransaction(ctx, tx4) + backend.SendTransaction(ctx, tx4, bind.PrivateTxArgs{}) case 2: // bankUser transfer some ether to signer bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, nil, nil), signer, bankKey) - backend.SendTransaction(ctx, tx1) + backend.SendTransaction(ctx, tx1, bind.PrivateTxArgs{}) // invoke test contract data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey) - backend.SendTransaction(ctx, tx2) + backend.SendTransaction(ctx, tx2, bind.PrivateTxArgs{}) case 3: // invoke test contract bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey) - backend.SendTransaction(ctx, tx) + backend.SendTransaction(ctx, tx, bind.PrivateTxArgs{}) } backend.Commit() } diff --git a/light/lightchain.go b/light/lightchain.go index 6fc321ae0b..a0a0988109 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -26,6 +26,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/consensus" "github.com/ethereum/go-ethereum/core" @@ -72,6 +74,9 @@ type LightChain struct { running int32 // whether LightChain is running or stopped procInterrupt int32 // interrupts chain insert disableCheckFreq int32 // disables header verification + + // Quorum + isMultitenant bool } // NewLightChain returns a fully initialised light chain using information @@ -118,6 +123,15 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. return bc, nil } +func NewMultitenantLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine, checkpoint *params.TrustedCheckpoint) (*LightChain, error) { + bc, err := NewLightChain(odr, config, engine, checkpoint) + if err != nil { + return nil, err + } + bc.isMultitenant = true + return bc, nil +} + // AddTrustedCheckpoint adds a trusted checkpoint to the blockchain func (lc *LightChain) AddTrustedCheckpoint(cp *params.TrustedCheckpoint) { if lc.odr.ChtIndexer() != nil { @@ -222,7 +236,7 @@ func (lc *LightChain) Genesis() *types.Block { return lc.genesisBlock } -func (lc *LightChain) StateCache() state.Database { +func (lc *LightChain) StateCache() (state.Database, state.Database) { panic("not implemented") } @@ -580,3 +594,7 @@ func (lc *LightChain) DisableCheckFreq() { func (lc *LightChain) EnableCheckFreq() { atomic.StoreInt32(&lc.disableCheckFreq, 0) } + +func (lc *LightChain) SupportsMultitenancy(context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) { + return nil, lc.isMultitenant +} diff --git a/light/odr_test.go b/light/odr_test.go index 78bf373e60..9c34d7fd00 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -196,7 +196,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain st.SetBalance(testBankAddress, math.MaxBig256) msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} context := core.NewEVMContext(msg, header, chain, nil) - vmenv := vm.NewEVM(context, st, config, vm.Config{}) + vmenv := vm.NewEVM(context, st, st, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp) res = append(res, result.Return()...) diff --git a/light/trie.go b/light/trie.go index 0d69e74e21..9330d176c2 100644 --- a/light/trie.go +++ b/light/trie.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -89,6 +91,25 @@ func (db *odrDatabase) TrieDB() *trie.Database { return nil } +type stubAccountExtraDataLinker struct { +} + +func newAccountExtraDataLinkerStub() rawdb.AccountExtraDataLinker { + return &stubAccountExtraDataLinker{} +} + +func (pml *stubAccountExtraDataLinker) GetAccountExtraDataRoot(_ common.Hash) common.Hash { + return common.Hash{} +} + +func (pml *stubAccountExtraDataLinker) Link(_, _ common.Hash) error { + return nil +} + +func (db *odrDatabase) AccountExtraDataLinker() rawdb.AccountExtraDataLinker { + return newAccountExtraDataLinkerStub() +} + type odrTrie struct { db *odrDatabase id *TrieID diff --git a/log/emit_checkpoint.go b/log/emit_checkpoint.go new file mode 100644 index 0000000000..bccfd6c7a1 --- /dev/null +++ b/log/emit_checkpoint.go @@ -0,0 +1,22 @@ +package log + +const ( + TxCreated = "TX-CREATED" + TxAccepted = "TX-ACCEPTED" + TxCompleted = "TX-COMPLETED" + BecameMinter = "BECAME-MINTER" + BecameVerifier = "BECAME-VERIFIER" + BecameLearner = "BECAME-LEARNER" + BlockCreated = "BLOCK-CREATED" + BlockVotingStarted = "BLOCK-VOTING-STARTED" +) + +var DoEmitCheckpoints = false + +func EmitCheckpoint(checkpointName string, logValues ...interface{}) { + args := []interface{}{"name", checkpointName} + args = append(args, logValues...) + if DoEmitCheckpoints { + Info("QUORUM-CHECKPOINT", args...) + } +} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..285424cd231b6574e0f0801078008d5ef7dc2342 GIT binary patch literal 8324 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 uint64(timestamp) { timestamp = int64(parent.Time() + 1) } + + allowedFutureBlockTime := int64(w.config.AllowedFutureBlockTime) //Quorum - get AllowedFutureBlockTime to fix issue # 1004 + // this will ensure we're not going off too far in the future - if now := time.Now().Unix(); timestamp > now+1 { + if now := time.Now().Unix(); timestamp > now+1+allowedFutureBlockTime { wait := time.Duration(timestamp-now) * time.Second log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) time.Sleep(wait) @@ -979,7 +1059,15 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st receipts[i] = new(types.Receipt) *receipts[i] = *l } + + privateReceipts := make([]*types.Receipt, len(w.current.privateReceipts)) + for i, l := range w.current.privateReceipts { + privateReceipts[i] = new(types.Receipt) + *privateReceipts[i] = *l + } + s := w.current.state.Copy() + ps := w.current.privateState.Copy() block, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts) if err != nil { return err @@ -989,7 +1077,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st interval() } select { - case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}: + case w.taskCh <- &task{receipts: receipts, privateReceipts: privateReceipts, state: s, privateState: ps, block: block, createdAt: time.Now()}: w.unconfirmed.Shift(block.NumberU64() - 1) feesWei := new(big.Int) diff --git a/miner/worker_test.go b/miner/worker_test.go index a5c558ba5f..772bf9ccdf 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -146,6 +146,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine } } +func (b *testWorkerBackend) ChainDb() ethdb.Database { return b.db } func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } func (b *testWorkerBackend) TxPool() *core.TxPool { return b.txPool } diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..47a3d29a3d --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,187 @@ +# Project information +site_name: Quorum +site_url: https://docs.goquorum.com/ +site_description: A permissioned implementation of Ethereum supporting data privacy +site_author: Quorum +copyright: Quorum 2019 + +# Repository +repo_name: quorum +repo_url: https://github.com/jpmorganchase/quorum + +nav: + - Home: index.md + - Getting Started: + - Quickstart: Getting Started/Getting Started Overview.md + - Installing: Getting Started/Installing.md + - Examples: Getting Started/Quorum-Examples.md + - Creating a Network From Scratch: Getting Started/Creating-A-Network-From-Scratch.md + - Running Quorum: Getting Started/running.md + - Quorum API: Getting Started/api.md + - Quorum GraphQL: Getting Started/graphql.md + - Migration: Getting Started/migration.md + - Quorum Features: + - Transaction/Contract Privacy: + - Overview: Privacy/Overview.md + - Privacy/Privacy-Manager.md + - Privacy/Lifecycle-of-a-private-transaction.md + - Tessera: + - What is Tessera: Privacy/Tessera/Tessera.md + - Configuration: + - Overview: Privacy/Tessera/Configuration/Configuration Overview.md + - Keys Config: Privacy/Tessera/Configuration/Keys.md + - TLS Config: Privacy/Tessera/Configuration/TLS.md + - Using CLI to override config: Privacy/Tessera/Configuration/Using CLI to override config.md + - Sample Configuration: Privacy/Tessera/Configuration/Sample Configuration.md + - Tessera Services: + - Transaction Manager: Privacy/Tessera/Tessera Services/Transaction Manager.md + - Enclave: Privacy/Tessera/Tessera Services/Enclave.md + - Keys: + - Key Generation: Privacy/Tessera/Tessera Services/Keys/Keys.md + - Setting up Hashicorp Vault: Privacy/Tessera/Tessera Services/Keys/Setting up a Hashicorp Vault.md + - Setting up Azure Key Vault: Privacy/Tessera/Tessera Services/Keys/Setting up an Azure Key Vault.md + - Setting up AWS Secrets Manager: Privacy/Tessera/Tessera Services/Keys/Setting up an AWS Secrets Manager.md + - Usage: + - Interfaces & API: Privacy/Tessera/Usage/Interface & API.md + - Admin Usage: Privacy/Tessera/Usage/Admin Usage.md + - Monitoring: Privacy/Tessera/Usage/Monitoring.md + - Logging: Privacy/Tessera/Usage/Logging.md + - Migration from Constellation: Privacy/Tessera/Migration from Constellation.md + - Constellation: + - What is Constellation: Privacy/Constellation/Constellation.md + - How it works: Privacy/Constellation/How constellation works.md + - Sample Configuration: Privacy/Constellation/Sample Configuration.md + - Running Constellation: Privacy/Constellation/Installation & Running.md + - Contract Extension: + - Overview: Privacy/Contract-Extension/Overview.md + - APIs: Privacy/Contract-Extension/ContractExtension apis.md + - Consensus: + - Consensus: Consensus/Consensus.md + - Raft BFT: + - Consensus/raft/raft.md + - Consensus/raft/raft-rpc-api.md + - Istanbul BFT: + - Overview: Consensus/ibft/ibft.md + - Consensus/ibft/istanbul-rpc-api.md + - Consensus/ibft/ibft-parameters.md + - Permissioning: + - Overview: Permissioning/Permissions Overview.md + - Basic: Permissioning/Basic NetworkPermissions.md + - Enhanced: + - Overview: Permissioning/Enhanced Permissions Model/Overview.md + - Design: Permissioning/Enhanced Permissions Model/Contract Design.md + - Setup: Permissioning/Enhanced Permissions Model/setup.md + - APIs: Permissioning/Enhanced Permissions Model/Permissioning apis.md + - Usage: Permissioning/Enhanced Permissions Model/Usage.md + - Account/Key Management: + - Overview: Account-Key-Management/Overview.md + - Quorum: + - Overview: Account-Key-Management/Quorum/Overview.md + - Keystore Files: Account-Key-Management/Quorum/Keystore-Files.md + - Clef: Account-Key-Management/Quorum/Clef.md + - account Plugins: + - Overview: Account-Key-Management/Quorum/account-Plugins/Overview.md + - Hashicorp Vault: + - Overview: Account-Key-Management/Quorum/account-Plugins/Hashicorp-Vault/Overview.md + - Quickstart: Account-Key-Management/Quorum/account-Plugins/Hashicorp-Vault/Quickstart.md + - Tessera: Account-Key-Management/Tessera/Overview.md + - Constellation: Account-Key-Management/Constellation/Overview.md + - Pluggable Architecture: + - Overview: PluggableArchitecture/Overview.md + - Settings: PluggableArchitecture/Settings.md + - Internals: PluggableArchitecture/Internals.md + - Plugins: + - helloworld: + - Interface: PluggableArchitecture/Plugins/helloworld/interface.md + - Implementation: PluggableArchitecture/Plugins/helloworld/implementation.md + - security: + - For Users: PluggableArchitecture/Plugins/security/For-Users.md + - For Developers: PluggableArchitecture/Plugins/security/For-Developers.md + - API: PluggableArchitecture/Plugins/security/interface.md + - account: + - For Users: PluggableArchitecture/Plugins/account/For-Users.md + - For Developers: PluggableArchitecture/Plugins/account/For-Developers.md + - API: PluggableArchitecture/Plugins/account/interface.md + - Plugin Development: PluggableArchitecture/PluginDevelopment.md + - DNS: Quorum Features/dns.md + - Securing JSON RPC: Quorum Features/rpc-security.md + - Quorum Projects: + - Quorum Wizard: + - Getting Started: Wizard/GettingStarted.md + - Interacting with the Network: Wizard/Interacting.md + - Cakeshop: + - Overview: Cakeshop/Overview.md + - Getting Started: Cakeshop/Getting started.md + - Cakeshop FAQ: Cakeshop/Cakeshop FAQ.md + - Quorum Profiling: + - Overview: Quorum Profiling/Overview.md + - Remix Plugin: + - Overview: RemixPlugin/Overview.md + - Getting Started: RemixPlugin/GettingStarted.md + - quorum.js: + - Overview: quorum.js/Overview.md + - extend API: quorum.js/extend.md + - RawTransactionManager API: quorum.js/RawTransactionManager.md + - Security Framework: + - Overview: Security/Framework/Overview.md + - Quorum Network: + - Consortium: Security/Framework/Quorum Network Security/Consortium.md + - Quorum Node: Security/Framework/Quorum Network Security/Node.md + - Transaction Manager: Security/Framework/Quorum Network Security/Transaction Manager.md + - Operational Considerations: Security/Framework/Quorum Network Security/Opertional Considerations.md + - Decentralized App: + - Frontend: Security/Framework/Decentralized Application/Frontend Components.md + - Smart Contracts: Security/Framework/Decentralized Application/Smart Contracts Security.md + - How-To Guides: + - Adding new nodes: How-To-Guides/adding_nodes.md + - Adding IBFT validators: How-To-Guides/add_ibft_validator.md + - Backup & Restore: How-To-Guides/import-export.md + - Quorum/Tessera HA Setup: How-To-Guides/HA_Setup.md + - Product Roadmap: roadmap.md + - FAQ: FAQ.md + +theme: + name: 'material' + custom_dir: 'docs/theme' + font: + text: 'Roboto' + code: 'Roboto Mono' + language: 'en' + logo: 'images/logo.png' + favicon: 'images/logo-48x48.png' + +extra_css: + - 'theme/assets/stylesheets/extra.css' + +markdown_extensions: + - toc: + permalink: true + toc_depth: 3 + - attr_list + - codehilite + - admonition + - footnotes + - def_list + - abbr + - pymdownx.arithmatex + - pymdownx.betterem: + smart_enable: all + - pymdownx.keys + - pymdownx.details + - pymdownx.emoji + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + - markdown_include.include: + base_path: docs + - meta + - smarty + - plantuml_markdown: + server: http://www.plantuml.com/plantuml + +plugins: + - search diff --git a/mobile/ethclient.go b/mobile/ethclient.go index 662125c4ad..32cd8b199f 100644 --- a/mobile/ethclient.go +++ b/mobile/ethclient.go @@ -21,6 +21,8 @@ package geth import ( "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ) @@ -312,5 +314,5 @@ func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (gas int64, _ // If the transaction was a contract creation use the TransactionReceipt method to get the // contract address after the transaction has been mined. func (ec *EthereumClient) SendTransaction(ctx *Context, tx *Transaction) error { - return ec.client.SendTransaction(ctx.context, tx.tx) + return ec.client.SendTransaction(ctx.context, tx.tx, bind.PrivateTxArgs{}) } diff --git a/multitenancy/authorization_provider.go b/multitenancy/authorization_provider.go new file mode 100644 index 0000000000..a3aed78c29 --- /dev/null +++ b/multitenancy/authorization_provider.go @@ -0,0 +1,222 @@ +package multitenancy + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" +) + +var ( + ErrNotAuthorized = errors.New("not authorized") + CtxKeyAuthorizeCreateFunc = "AUTHORIZE_CREATE_FUNC" + CtxKeyAuthorizeMessageCallFunc = "AUTHORIZE_MESSAGE_CALL_FUNC" +) + +// AccountAuthorizationProvider performs authorization checks for Ethereum Account +// 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 AccountAuthorizationProvider interface { + IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attr *AccountStateSecurityAttribute) (bool, error) +} + +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) + +// ContractAuthorizationProvider performs authorization checks for contract +// based on what is entitled in the proto.PreAuthenticatedAuthenticationToken +// and what is asked in ContractSecurityAttribute list. +type ContractAuthorizationProvider interface { + IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*ContractSecurityAttribute) (bool, error) +} + +type DefaultContractAuthorizationProvider struct { +} + +// isAuthorized performs authorization check for one security attribute against +// the granted access inside the pre-authenticated access token. +func (cm *DefaultContractAuthorizationProvider) isAuthorized(authToken *proto.PreAuthenticatedAuthenticationToken, attr *ContractSecurityAttribute) (bool, error) { + query := url.Values{} + switch attr.Visibility { + case VisibilityPublic: + switch attr.Action { + case ActionRead, ActionWrite, ActionCreate: + if (attr.To == common.Address{}) { + query.Set(QueryOwnedEOA, toHexAddress(attr.From)) + } else { + query.Set(QueryOwnedEOA, toHexAddress(attr.To)) + } + } + case VisibilityPrivate: + switch attr.Action { + case ActionRead, ActionWrite: + if (attr.To == common.Address{}) { + query.Set(QueryOwnedEOA, toHexAddress(attr.From)) + } else { + query.Set(QueryOwnedEOA, toHexAddress(attr.To)) + } + for _, tm := range attr.Parties { + query.Add(QueryFromTM, tm) + } + case ActionCreate: + query.Set(QueryFromTM, attr.PrivateFrom) + } + } + // construct request permission identifier + request, err := url.Parse(fmt.Sprintf("%s://%s/%s/%s?%s", attr.Visibility, toHexAddress(attr.From), attr.Action, "contracts", query.Encode())) + if err != nil { + return false, err + } + // compare the contract security attribute with the consolidate list + for _, granted := range authToken.GetAuthorities() { + pi, err := url.Parse(granted.GetRaw()) + if err != nil { + continue + } + granted := pi.String() + ask := request.String() + isMatched := match(attr, request, pi) + log.Debug("Checking contract access", "passed", isMatched, "granted", granted, "ask", ask) + if isMatched { + return true, nil + } + } + return false, nil +} + +// IsAuthorized performs authorization check for each security attribute against +// the granted access inside the pre-authenticated access token. +// +// All security attributes must pass. +func (cm *DefaultContractAuthorizationProvider) IsAuthorized(_ context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*ContractSecurityAttribute) (bool, error) { + if len(attributes) == 0 { + return false, nil + } + for _, attr := range attributes { + isMatched, err := cm.isAuthorized(authToken, attr) + if err != nil { + return false, err + } + if !isMatched { + return false, nil + } + } + return true, nil +} + +func toHexAddress(a common.Address) string { + if (a == common.Address{}) { + return AnyEOAAddress + } + return strings.ToLower(a.Hex()) +} + +func match(attr *ContractSecurityAttribute, ask, granted *url.URL) bool { + askScheme := strings.ToLower(ask.Scheme) + if allowedPublic(askScheme) { + return true + } + + isPathMatched := matchPath(strings.ToLower(ask.Path), strings.ToLower(granted.Path)) + return askScheme == strings.ToLower(granted.Scheme) && //Note: "askScheme" here is "private" since we checked VisibilityPublic above. + matchHost(attr.Action, strings.ToLower(ask.Host), strings.ToLower(granted.Host)) && //whether i have permission to execute using this ethereum address + isPathMatched && //is our permission for the same action (read, write, deploy) + matchQuery(attr, ask.Query(), granted.Query()) +} + +func allowedPublic(scheme string) bool { + return scheme == string(VisibilityPublic) +} + +func matchHost(a ContractAction, ask string, granted string) bool { + // for READ action, we use owned.eoa query param instead + return granted == AnyEOAAddress || ask == granted || a == ActionRead +} + +func matchPath(ask string, granted string) bool { + return strings.HasPrefix(granted, "/_") || ask == granted +} + +func matchQuery(attr *ContractSecurityAttribute, ask, granted url.Values) bool { + // if asking nothing, we should bail out + if len(ask) == 0 || len(ask[QueryFromTM]) == 0 { + return false + } + // possible scenarios: + // 1. read/write -> from.tm -> at least 1 of the same key must appear in both lists + // 2. read/write - owned.eoa/to.eoa -> check subset + // 3. create -> from.tm/owned.eoa/to.eoa -> check subset + for k, askValues := range ask { + grantedValues := granted[k] + switch attr.Action { + case ActionRead, ActionWrite: + // Scenario 1 + if k == QueryFromTM { + if isIntersectionEmpty(grantedValues, askValues) { + return false + } + } + //Scenario 2 + if k == QueryOwnedEOA || k == QueryToEOA { + if !subset(grantedValues, askValues) { + return false + } + } + case ActionCreate: + //Scenario 3 + if !subset(grantedValues, askValues) { + return false + } + default: + // we don't know, better reject + log.Error("unsupported action", "action", attr.Action) + return false + } + } + return true +} + +func subset(grantedValues, askValues []string) bool { + for _, askValue := range askValues { + found := false + sanitizedAskValue := askValue + if strings.HasPrefix(askValue, "0x") { + sanitizedAskValue = strings.ToLower(askValue) + } + for _, grantedValue := range grantedValues { + sanitizedGrantedValue := grantedValue + if strings.HasPrefix(grantedValue, "0x") { + sanitizedGrantedValue = strings.ToLower(grantedValue) + } + if sanitizedGrantedValue == AnyEOAAddress || sanitizedAskValue == sanitizedGrantedValue { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +func isIntersectionEmpty(grantedValues, askValues []string) bool { + grantedMap := make(map[string]bool) + for _, grantedVal := range grantedValues { + grantedMap[grantedVal] = true + } + for _, askVal := range askValues { + if grantedMap[askVal] { + return false + } + } + return true +} diff --git a/multitenancy/authorization_provider_test.go b/multitenancy/authorization_provider_test.go new file mode 100644 index 0000000000..43ac7fbb1c --- /dev/null +++ b/multitenancy/authorization_provider_test.go @@ -0,0 +1,861 @@ +package multitenancy + +import ( + "context" + "net/url" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + "github.com/stretchr/testify/assert" +) + +func init() { + log.Root().SetHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) +} + +type testCase struct { + msg string + granted []string + ask []*ContractSecurityAttribute + isAuthorized bool +} + +func TestMatch_whenTypical(t *testing.T) { + granted, _ := url.Parse("private://0xa1b1c1/create/contracts?from.tm=A/") + ask, _ := url.Parse("private://0xa1b1c1/create/contracts?from.tm=A%2F") + + assert.True(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func TestMatch_whenAskNothing(t *testing.T) { + granted, _ := url.Parse("private://0x0/_/contracts?from.tm=A&owned.eoa=0x0") + ask, _ := url.Parse("private://0xa1b1c1/write/contracts?owned.eoa=0xe1e1e1") + + assert.False(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) + + ask, _ = url.Parse("private://0xa1b1c1/write/contracts") + + assert.False(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func TestMatch_whenGrantNothing(t *testing.T) { + granted, _ := url.Parse("private://0xa1b1c1/write/contracts") + ask, _ := url.Parse("private://0xa1b1c1/write/contracts?from.tm=A") + + assert.False(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func TestMatch_whenAnyAction(t *testing.T) { + granted, _ := url.Parse("private://0xa1b1c1/_/contracts?owned.eoa=0x0&from.tm=A1") + ask, _ := url.Parse("private://0xa1b1c1/read/contracts?from.tm=A1") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) + + ask, _ = url.Parse("private://0xa1b1c1/read/contracts?owned.eoa=0x0&from.tm=A1&from.tm=B1") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) + + ask, _ = url.Parse("private://0xa1b1c1/write/contracts?owned.eoa=0x0&from.tm=A1") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionWrite, + }, ask, granted)) +} + +func TestMatch_whenPathNotMatched(t *testing.T) { + granted, _ := url.Parse("private://0xa1b1c1/write/contracts?owned.eoa=0x0&from.tm=A1") + ask, _ := url.Parse("private://0xa1b1c1/read/contracts?from.tm=A1") + + assert.False(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenSchemeIsNotEqual(t *testing.T) { + granted, _ := url.Parse("unknown://0xa1b1c1/create/contracts?from.tm=A") + ask, _ := url.Parse("private://0xa1b1c1/create/contracts?from.tm=A") + + assert.False(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func TestMatch_whenContractWritePermission_GrantedIsTheSuperSet(t *testing.T) { + granted, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=A&from.tm=B") + ask, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionWrite, + }, ask, granted), "with write permission") + + granted, _ = url.Parse("private://0x0/read/contracts?owned.eoa=0x0&from.tm=A&from.tm=B") + ask, _ = url.Parse("private://0x0/read/contracts?owned.eoa=0x0&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted), "with read permission") +} + +func TestMatch_whenContractReadPermission_AnyAction(t *testing.T) { + granted, _ := url.Parse("private://0x1234/_/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/read/contracts?owned.eoa=0x1234&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenContractReadPermission_AnyEoa(t *testing.T) { + granted, _ := url.Parse("private://0x1234/_/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/read/contracts?owned.eoa=0x0&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenContractReadPermission_EoaDifferent(t *testing.T) { + granted, _ := url.Parse("private://0x0/read/contracts?owned.eoa=0x095e7baea6a6c7c4c2dfeb977efac326af552d87&from.tm=A") + ask, _ := url.Parse("private://0x0/read/contracts?owned.eoa=0x945304eb96065b2a98b57a48a06ae28d285a71b5&from.tm=A") + + assert.False(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenContractReadPermission_EoaSame(t *testing.T) { + granted, _ := url.Parse("private://0x0/read/contracts?owned.eoa=0x095e7baea6a6c7c4c2dfeb977efac326af552d87&from.tm=A") + ask, _ := url.Parse("private://0x0/read/contracts?owned.eoa=0x095e7baea6a6c7c4c2dfeb977efac326af552d87&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenContractReadPermission_TmKeysIntersect(t *testing.T) { + granted, _ := url.Parse("private://0x0/read/contracts?from.tm=A&from.tm=B") + ask, _ := url.Parse("private://0x0/read/contracts?from.tm=B&from.tm=C") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenContractReadPermission_TmKeysDontIntersect(t *testing.T) { + granted, _ := url.Parse("private://0x0/read/contracts?from.tm=A&from.tm=B") + ask, _ := url.Parse("private://0x0/read/contracts?from.tm=C&from.tm=D") + + assert.False(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionRead, + }, ask, granted)) +} + +func TestMatch_whenContractWritePermission_Same(t *testing.T) { + granted, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionWrite, + }, ask, granted)) +} + +func TestMatch_whenContractWritePermission_Different(t *testing.T) { + granted, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=B") + + assert.False(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionWrite, + }, ask, granted)) +} + +func TestMatch_whenContractWritePermission_AskIsSuperSet(t *testing.T) { + granted, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/write/contracts?owned.eoa=0x0&from.tm=B&from.tm=C&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionWrite, + }, ask, granted)) +} + +func TestMatch_whenContractCreatePermission_Same(t *testing.T) { + granted, _ := url.Parse("private://0x0/create/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/create/contracts?owned.eoa=0x0&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionCreate, + }, ask, granted)) +} + +func TestMatch_whenContractCreatePermission_Different(t *testing.T) { + granted, _ := url.Parse("private://0x0/create/contracts?owned.eoa=0x0&from.tm=A") + ask, _ := url.Parse("private://0x0/create/contracts?owned.eoa=0x0&from.tm=B") + + assert.False(t, match(&ContractSecurityAttribute{ + Visibility: VisibilityPrivate, + Action: ActionCreate, + }, ask, granted)) +} + +func TestMatch_whenUsingWildcardAccount(t *testing.T) { + granted, _ := url.Parse("private://0x0/create/contracts?from.tm=dLHrFQpbSda0EhJnLonsBwDjks%2Bf724NipfI5zK5RSs%3D") + ask, _ := url.Parse("private://0xed9d02e382b34818e88b88a309c7fe71e65f419d/create/contracts?from.tm=dLHrFQpbSda0EhJnLonsBwDjks%2Bf724NipfI5zK5RSs%3D") + + assert.True(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) + + granted, _ = url.Parse("private://0x0/read/contract?owned.eoa=0x0&from.tm=A") + ask, _ = url.Parse("private://0xa1b1c1/read/contract?owned.eoa=0x1234&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{Action: ActionRead}, ask, granted)) +} + +func TestMatch_whenNotUsingWildcardAccount(t *testing.T) { + granted, _ := url.Parse("private://0xed9d02e382b34818e88b88a309c7fe71e65f419d/create/contracts?from.tm=dLHrFQpbSda0EhJnLonsBwDjks%2Bf724NipfI5zK5RSs%3D") + ask, _ := url.Parse("private://0xed9d02e382b34818e88b88a309c7fe71e65f419d/create/contracts?from.tm=dLHrFQpbSda0EhJnLonsBwDjks%2Bf724NipfI5zK5RSs%3D") + + assert.True(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) + + granted, _ = url.Parse("private://0x0/read/contract?owned.eoa=0x0&from.tm=A") + ask, _ = url.Parse("private://0xa1b1c1/read/contract?owned.eoa=0x1234&from.tm=A") + + assert.True(t, match(&ContractSecurityAttribute{Action: ActionRead}, ask, granted)) +} + +func TestMatch_failsWhenAccountsDiffer(t *testing.T) { + granted, _ := url.Parse("private://0xed9d02e382b34818e88b88a309c7fe71e65f419d/create/contracts?from.tm=dLHrFQpbSda0EhJnLonsBwDjks%2Bf724NipfI5zK5RSs%3D") + ask, _ := url.Parse("private://0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b/create/contracts?from.tm=dLHrFQpbSda0EhJnLonsBwDjks%2Bf724NipfI5zK5RSs%3D") + + assert.False(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func TestMatch_whenPublic(t *testing.T) { + granted, _ := url.Parse("private://0xa1b1c1/create/contract?from.tm=A/") + ask, _ := url.Parse("public://0x0/create/contract") + + assert.True(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func TestMatch_whenNotEscaped(t *testing.T) { + // query not escaped probably in the granted authority resource identitifer + granted, _ := url.Parse("private://0xed9d02e382b34818e88b88a309c7fe71e65f419d/create/contracts?from.tm=BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=") + ask, _ := url.Parse("private://0xed9d02e382b34818e88b88a309c7fe71e65f419d/create/contracts?from.tm=BULeR8JyUWhiuuCMU%2FHLA0Q5pzkYT%2BcHII3ZKBey3Bo%3D") + + assert.False(t, match(&ContractSecurityAttribute{Action: ActionCreate}, ask, granted)) +} + +func runTestCases(t *testing.T, testCases []*testCase) { + testObject := &DefaultContractAuthorizationProvider{} + for _, tc := range testCases { + log.Debug("--> Running test case: " + tc.msg) + authorities := make([]*proto.GrantedAuthority, 0) + for _, a := range tc.granted { + authorities = append(authorities, &proto.GrantedAuthority{Raw: a}) + } + b, err := testObject.IsAuthorized( + context.Background(), + &proto.PreAuthenticatedAuthenticationToken{Authorities: authorities}, + tc.ask...) + if !assert.NoError(t, err, tc.msg) { + return + } + if !assert.Equal(t, tc.isAuthorized, b, tc.msg) { + return + } + } +} + +func TestDefaultAccountAccessDecisionManager_IsAuthorized_forPublicContracts(t *testing.T) { + runTestCases(t, []*testCase{ + canCreatePublicContracts, + // canNotCreatePublicContracts, + canReadOwnedPublicContracts, + canReadOtherPublicContracts, + // canNotReadOtherPublicContracts, + canWriteOwnedPublicContracts, + canWriteOtherPublicContracts1, + canWriteOtherPublicContracts2, + // canNotWriteOtherPublicContracts, + canCreatePublicContractsAndWriteToOthers, + }) +} + +func TestDefaultAccountAccessDecisionManager_IsAuthorized_forPrivateContracts(t *testing.T) { + runTestCases(t, []*testCase{ + canCreatePrivateContracts, + canNotCreatePrivateContracts, + canReadOwnedPrivateContracts, + canReadOtherPrivateContracts, + canNotReadOtherPrivateContracts, + canNotReadOtherPrivateContractsNoPrivy, + canWriteOwnedPrivateContracts, + canWriteOtherPrivateContracts, + canWriteOtherPrivateContractsWithOverlappedScope, + canNotWriteOtherPrivateContracts, + canNotWriteOtherPrivateContractsNoPrivy, + }) +} + +func TestDefaultAccountAccessDecisionManager_IsAuthorized_forPrivateContracts_wildcards_whenCreate(t *testing.T) { + fullAccessToX := []string{ + "private://0x0/_/contracts?owned.eoa=0x0&from.tm=X", + } + runTestCases(t, []*testCase{ + { + msg: "X has full access to a private contract when create", + isAuthorized: true, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + // create + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionCreate, + PrivateFrom: "X", + Parties: []string{}, + }, + }, + }, + { + msg: "X can't creat private contract with other TM key", + isAuthorized: false, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + // create + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionCreate, + PrivateFrom: "A", + Parties: []string{}, + }, + }, + }, + }) +} + +func TestDefaultAccountAccessDecisionManager_IsAuthorized_forPrivateContracts_wildcards_whenRead(t *testing.T) { + fullAccessToX := []string{ + "private://0x0/_/contracts?owned.eoa=0x0&from.tm=X", + } + runTestCases(t, []*testCase{ + { + msg: "X has full access to a private contract when read as one of the participants", + isAuthorized: true, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Visibility: VisibilityPrivate, + Action: ActionRead, + PrivateFrom: "X", + Parties: []string{"X", "Y"}, + }, + }, + }, + { + msg: "X has full access to a private contract when read as a single participant", + isAuthorized: true, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Visibility: VisibilityPrivate, + Action: ActionRead, + PrivateFrom: "X", + Parties: []string{"X"}, + }, + }, + }, + { + msg: "X can't read other private contracts", + isAuthorized: false, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Visibility: VisibilityPrivate, + Action: ActionRead, + PrivateFrom: "X", + Parties: []string{"A", "B"}, + }, + }, + }, + { + msg: "X can't read other private contracts by faking the read", + isAuthorized: false, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Visibility: VisibilityPrivate, + Action: ActionRead, + PrivateFrom: "A", + Parties: []string{"A", "B"}, + }, + }, + }, + { + msg: "X can't read other private contracts when proxy-read", + isAuthorized: false, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + // read its own contract + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Visibility: VisibilityPrivate, + Action: ActionRead, + PrivateFrom: "X", + Parties: []string{"X"}, + }, + // but using it as proxy to read other contract + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Visibility: VisibilityPrivate, + Action: ActionRead, + PrivateFrom: "X", + Parties: []string{"A", "B"}, + }, + }, + }, + }) +} + +func TestDefaultAccountAccessDecisionManager_IsAuthorized_forPrivateContracts_wildcards_whenWrite(t *testing.T) { + fullAccessToX := []string{ + "private://0x0/_/contracts?owned.eoa=0x0&from.tm=X", + } + runTestCases(t, []*testCase{ + { + msg: "X has full access to a private contract when write as a single participant", + isAuthorized: true, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "X", + Parties: []string{"X"}, + }, + }, + }, + { + msg: "X has full access to a private contract when write as one of the participants", + isAuthorized: true, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "X", + Parties: []string{"X", "Y"}, + }, + }, + }, + { + msg: "X must not access other private contracts when faking write", + isAuthorized: false, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), // creator EOA address + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "A", + Parties: []string{"A", "B"}, + }, + }, + }, + { + msg: "X can not write to a private contract not privy to X", + isAuthorized: false, + granted: fullAccessToX, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), // creator EOA address + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "X", + Parties: []string{"A", "B"}, + }, + }, + }, + }) +} + +var ( + canCreatePublicContracts = &testCase{ + msg: "0x0a1a1a1 can create public contracts", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/create/contracts", + }, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPublic, + Action: ActionCreate, + }, + }, + isAuthorized: true, + } + canCreatePublicContractsAndWriteToOthers = &testCase{ + msg: "0x0a1a1a1 can create public contracts and write to contracts created by 0xb1b1b1", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/create/contracts", + "public://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&owned.eoa=0x0000000000000000000000000000000000c1c1c1", + }, + ask: []*ContractSecurityAttribute{ + { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPublic, + Action: ActionCreate, + }, { + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPublic, + Action: ActionWrite, + }, + }, + isAuthorized: true, + } + // + //canNotCreatePublicContracts = &testCase{ + // msg: "0xb1b1b1 can not create public contracts", + // granted: []string{ + // "public://0x0000000000000000000000000000000000a1a1a1/create/contracts", + // "public://0x0000000000000000000000000000000000b1b1b1/read/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1", + // }, + // ask: []*ContractSecurityAttribute{{ + // AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + // From: common.HexToAddress("0xb1b1b1"), + // }, + // Visibility: VisibilityPublic, + // Action: ActionCreate, + // }}, + // isAuthorized: false, + //} + canReadOwnedPublicContracts = &testCase{ + msg: "0x0a1a1a1 can read public contracts created by self", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPublic, + Action: ActionRead, + }}, + isAuthorized: true, + } + canReadOtherPublicContracts = &testCase{ + msg: "0x0a1a1a1 can read public contracts created by 0xb1b1b1", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPublic, + Action: ActionRead, + }}, + isAuthorized: true, + } + //canNotReadOtherPublicContracts = &testCase{ + // msg: "0x0a1a1a1 can only read public contracts created by self", + // granted: []string{ + // "public://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1", + // }, + // ask: []*ContractSecurityAttribute{{ + // AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + // From: common.HexToAddress("0xa1a1a1"), + // To: common.HexToAddress("0xb1b1b1"), + // }, + // Visibility: VisibilityPublic, + // Action: ActionRead, + // }}, + // isAuthorized: false, + //} + canWriteOwnedPublicContracts = &testCase{ + msg: "0x0a1a1a1 can send transactions to public contracts created by self", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPublic, + Action: ActionWrite, + }}, + isAuthorized: true, + } + canWriteOtherPublicContracts1 = &testCase{ + msg: "0xa1a1a1 can send transactions to public contracts created by 0xb1b1b1", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&owned.eoa=0x0000000000000000000000000000000000c1c1c1", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPublic, + Action: ActionWrite, + }}, + isAuthorized: true, + } + canWriteOtherPublicContracts2 = &testCase{ + msg: "0xa1a1a1 can send transactions to public contracts created by 0xb1b1b1", + granted: []string{ + "public://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&owned.eoa=0x0000000000000000000000000000000000c1c1c1", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xc1c1c1"), + }, + Visibility: VisibilityPublic, + Action: ActionWrite, + }}, + isAuthorized: true, + } + //canNotWriteOtherPublicContracts = &testCase{ + // msg: "0x0a1a1a1 can only send transactions to public contracts created by self", + // granted: []string{ + // "public://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1", + // "public://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1", + // }, + // ask: []*ContractSecurityAttribute{{ + // AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + // From: common.HexToAddress("0xa1a1a1"), + // To: common.HexToAddress("0xb1b1b1"), + // }, + // Visibility: VisibilityPublic, + // Action: ActionWrite, + // }}, + // isAuthorized: false, + //} + // private contracts + canCreatePrivateContracts = &testCase{ + msg: "0x0a1a1a1 can create private contracts with sender key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/create/contracts?from.tm=A", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionCreate, + PrivateFrom: "A", + Parties: []string{}, + }}, + isAuthorized: true, + } + canNotCreatePrivateContracts = &testCase{ + msg: "0x0a1a1a1 can NOT create private contracts with sender key A if only own key B", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/create/contracts?from.tm=B", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionCreate, + PrivateFrom: "A", + Parties: []string{}, + }}, + isAuthorized: false, + } + canReadOwnedPrivateContracts = &testCase{ + msg: "0x0a1a1a1 can read private contracts created by self and was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1&from.tm=A&from.tm=B", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionRead, + Parties: []string{"A"}, + }}, + isAuthorized: true, + } + canReadOtherPrivateContracts = &testCase{ + msg: "0x0a1a1a1 can read private contracts created by 0xb1b1b1 and was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&from.tm=A", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionRead, + Parties: []string{"A"}, + }}, + isAuthorized: true, + } + canNotReadOtherPrivateContracts = &testCase{ + msg: "0x0a1a1a1 can NOT read private contracts created by 0xb1b1b1 even it was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000c1c1c1&from.tm=A", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionRead, + Parties: []string{"A"}, + }}, + isAuthorized: false, + } + canNotReadOtherPrivateContractsNoPrivy = &testCase{ + msg: "0x0a1a1a1 can NOT read private contracts created by 0xb1b1b1 as it was privy to a key B", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/read/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&from.tm=B", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionRead, + Parties: []string{"A"}, + }}, + isAuthorized: false, + } + canWriteOwnedPrivateContracts = &testCase{ + msg: "0x0a1a1a1 can write private contracts created by self and was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000a1a1a1&from.tm=A&from.tm=B", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "A", + Parties: []string{"A"}, + }}, + isAuthorized: true, + } + canWriteOtherPrivateContracts = &testCase{ + msg: "0x0a1a1a1 can write private contracts created by 0xb1b1b1 and was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&from.tm=A", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "A", + Parties: []string{"A"}, + }}, + isAuthorized: true, + } + canWriteOtherPrivateContractsWithOverlappedScope = &testCase{ + msg: "0x0a1a1a1 can write private contracts created by 0xb1b1b1 and was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&from.tm=A", + "private://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&from.tm=A&from.tm=B", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + PrivateFrom: "A", + Parties: []string{"A"}, + }}, + isAuthorized: true, + } + canNotWriteOtherPrivateContracts = &testCase{ + msg: "0x0a1a1a1 can NOT write private contracts created by 0xb1b1b1 even it was privy to a key A", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000c1c1c1&from.tm=A", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + Parties: []string{"A"}, + }}, + isAuthorized: false, + } + canNotWriteOtherPrivateContractsNoPrivy = &testCase{ + msg: "0x0a1a1a1 can NOT write private contracts created by 0xb1b1b1 as it was privy to a key B", + granted: []string{ + "private://0x0000000000000000000000000000000000a1a1a1/write/contracts?owned.eoa=0x0000000000000000000000000000000000b1b1b1&from.tm=B", + }, + ask: []*ContractSecurityAttribute{{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{ + From: common.HexToAddress("0xa1a1a1"), + To: common.HexToAddress("0xb1b1b1"), + }, + Visibility: VisibilityPrivate, + Action: ActionWrite, + Parties: []string{"A"}, + }}, + isAuthorized: false, + } +) diff --git a/multitenancy/types.go b/multitenancy/types.go new file mode 100644 index 0000000000..4ee3f625a0 --- /dev/null +++ b/multitenancy/types.go @@ -0,0 +1,200 @@ +package multitenancy + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" +) + +type ContractVisibility string +type ContractAction string + +const ( + VisibilityPublic ContractVisibility = "public" + VisibilityPrivate ContractVisibility = "private" + ActionRead ContractAction = "read" + ActionWrite ContractAction = "write" + ActionCreate ContractAction = "create" + + // QueryOwnedEOA query parameter is to capture the EOA address + // For value transfer, it represents the account owner + // For message call, it represents the EOA that signed the contract creation transaction + // in other words, the EOA that owns the contract + QueryOwnedEOA = "owned.eoa" + // QueryToEOA query parameter is to capture the EOA address which is the + // target account in value transfer scenarios + QueryToEOA = "to.eoa" + // QueryFromTM query parameter is to capture the Tessera Public Key + // which indicates the sender of a private transaction or participant of a private contract + QueryFromTM = "from.tm" + + // AnyEOAAddress represents wild card for EOA address + AnyEOAAddress = "0x0" +) + +// Multitenancy support +type ContextAware interface { + SupportsMultitenancy(ctx context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) +} + +// AuthorizationProvider specifies APIs to be implemented to provide multitenancy capability +type AuthorizationProvider interface { + ContextAware + ContractAuthorizationProvider +} + +// AccountStateSecurityAttribute contains security configuration ask +// which are defined for a secure account state +type AccountStateSecurityAttribute struct { + From common.Address // Ethereum Account Address + To common.Address +} + +func (assa *AccountStateSecurityAttribute) String() string { + return fmt.Sprintf("from=%s to=%s", assa.From.Hex(), assa.To.Hex()) +} + +// ContractSecurityAttribute contains security configuration ask +// which are defined for a secure contract account +type ContractSecurityAttribute struct { + *AccountStateSecurityAttribute + Visibility ContractVisibility // public/private + Action ContractAction // create/read/write + PrivateFrom string // TM Key, only if Visibility is private, for write/create + Parties []string // TM Keys, only if Visibility is private, for read +} + +func (csa *ContractSecurityAttribute) String() string { + return fmt.Sprintf("%v visibility=%s action=%s privateFrom=%s parties=%v", csa.AccountStateSecurityAttribute, csa.Visibility, csa.Action, csa.PrivateFrom, csa.Parties) +} + +type ContractSecurityAttributeBuilder struct { + secAttr ContractSecurityAttribute +} + +func NewContractSecurityAttributeBuilder() *ContractSecurityAttributeBuilder { + return &ContractSecurityAttributeBuilder{ + secAttr: ContractSecurityAttribute{ + AccountStateSecurityAttribute: &AccountStateSecurityAttribute{}, + Parties: make([]string, 0), + }, + } +} + +func (csab *ContractSecurityAttributeBuilder) FromEOA(eoa common.Address) *ContractSecurityAttributeBuilder { + csab.secAttr.AccountStateSecurityAttribute.From = eoa + return csab +} + +// ethereum account destination +func (csab *ContractSecurityAttributeBuilder) ToEOA(eoa common.Address) *ContractSecurityAttributeBuilder { + csab.secAttr.AccountStateSecurityAttribute.To = eoa + return csab +} + +func (csab *ContractSecurityAttributeBuilder) PrivateFrom(tmPubKey string) *ContractSecurityAttributeBuilder { + csab.secAttr.PrivateFrom = tmPubKey + return csab +} + +// set privateFrom only if b is true, ignore otherwise +func (csab *ContractSecurityAttributeBuilder) PrivateFromOnlyIf(b bool, tmPubKey string) *ContractSecurityAttributeBuilder { + if b { + csab.secAttr.PrivateFrom = tmPubKey + } + return csab +} + +func (csab *ContractSecurityAttributeBuilder) Visibility(v ContractVisibility) *ContractSecurityAttributeBuilder { + csab.secAttr.Visibility = v + return csab +} + +func (csab *ContractSecurityAttributeBuilder) Private() *ContractSecurityAttributeBuilder { + return csab.Visibility(VisibilityPrivate) +} + +// set VisibilityPrivate if b is true, VisibilityPublic otherwise +func (csab *ContractSecurityAttributeBuilder) PrivateIf(b bool) *ContractSecurityAttributeBuilder { + if b { + return csab.Visibility(VisibilityPrivate) + } else { + return csab.Visibility(VisibilityPublic) + } +} + +func (csab *ContractSecurityAttributeBuilder) Public() *ContractSecurityAttributeBuilder { + return csab.Visibility(VisibilityPublic) +} + +func (csab *ContractSecurityAttributeBuilder) Action(a ContractAction) *ContractSecurityAttributeBuilder { + csab.secAttr.Action = a + return csab +} + +func (csab *ContractSecurityAttributeBuilder) Create() *ContractSecurityAttributeBuilder { + return csab.Action(ActionCreate) +} + +func (csab *ContractSecurityAttributeBuilder) Read() *ContractSecurityAttributeBuilder { + return csab.Action(ActionRead) +} + +func (csab *ContractSecurityAttributeBuilder) Write() *ContractSecurityAttributeBuilder { + return csab.Action(ActionWrite) +} + +// set ActionRead only if b is true, ignore otherwise +func (csab *ContractSecurityAttributeBuilder) ReadOnlyIf(b bool) *ContractSecurityAttributeBuilder { + if b { + return csab.Action(ActionRead) + } else { + return csab + } +} + +// set ActionWrite only if b is true, ignore otherwise +func (csab *ContractSecurityAttributeBuilder) WriteOnlyIf(b bool) *ContractSecurityAttributeBuilder { + if b { + return csab.Action(ActionWrite) + } else { + return csab + } +} + +// set Parties only if b is true, ignore otherwise +func (csab *ContractSecurityAttributeBuilder) PartiesOnlyIf(b bool, tmPubKeys []string) *ContractSecurityAttributeBuilder { + if b { + return csab.Parties(tmPubKeys) + } + return csab +} + +func (csab *ContractSecurityAttributeBuilder) Parties(tmPubKeys []string) *ContractSecurityAttributeBuilder { + parties := make([]string, len(tmPubKeys)) + copy(parties, tmPubKeys) + csab.secAttr.Parties = parties + return csab +} + +func (csab *ContractSecurityAttributeBuilder) Party(tmPubKey string) *ContractSecurityAttributeBuilder { + csab.secAttr.Parties = append(csab.secAttr.Parties, tmPubKey) + return csab +} + +func (csab *ContractSecurityAttributeBuilder) Build() *ContractSecurityAttribute { + return &csab.secAttr +} + +// FullAccessContractSecurityAttributes returns a list of contract security attributes. +// The attributes are used to verify ownership of a TM key which is going to be used +// to send a private transaction. +func FullAccessContractSecurityAttributes(fromEOA common.Address, privateFrom string) []*ContractSecurityAttribute { + return []*ContractSecurityAttribute{ + NewContractSecurityAttributeBuilder().FromEOA(fromEOA).Private().Create().PrivateFrom(privateFrom).Build(), + NewContractSecurityAttributeBuilder().FromEOA(fromEOA).Private().Write().Party(privateFrom).Build(), + NewContractSecurityAttributeBuilder().FromEOA(fromEOA).Private().Read().Party(privateFrom).Build(), + } +} diff --git a/node/api.go b/node/api.go index 1a73d1321d..99970174a3 100644 --- a/node/api.go +++ b/node/api.go @@ -264,6 +264,12 @@ type PublicAdminAPI struct { node *Node // Node interfaced by this API } +// Quorum: an extended nodeInfo to include plugin details for current node +type QuorumNodeInfo struct { + *p2p.NodeInfo + Plugins interface{} `json:"plugins"` +} + // NewPublicAdminAPI creates a new API definition for the public admin methods // of the node itself. func NewPublicAdminAPI(node *Node) *PublicAdminAPI { @@ -282,12 +288,15 @@ func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { // NodeInfo retrieves all the information we know about the host node at the // protocol granularity. -func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { +func (api *PublicAdminAPI) NodeInfo() (*QuorumNodeInfo, error) { server := api.node.Server() if server == nil { return nil, ErrNodeStopped } - return server.NodeInfo(), nil + return &QuorumNodeInfo{ + NodeInfo: server.NodeInfo(), + Plugins: api.node.PluginManager().PluginsInfo(), + }, nil } // Datadir retrieves the current data directory the node is using. diff --git a/node/config.go b/node/config.go index 61566b7bee..2ceffad534 100644 --- a/node/config.go +++ b/node/config.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/pluggable" "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/common" @@ -36,6 +37,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/plugin" "github.com/ethereum/go-ethereum/rpc" ) @@ -191,6 +194,9 @@ type Config struct { staticNodesWarning bool trustedNodesWarning bool oldGethResourceWarning bool + Plugins *plugin.Settings `toml:",omitempty"` + // Quorum: EnableNodePermission comes from EnableNodePermissionFlag --permissioned. + EnableNodePermission bool `toml:",omitempty"` } // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into @@ -463,6 +469,40 @@ func (c *Config) AccountConfig() (int, int, string, error) { return scryptN, scryptP, keydir, err } +// Quorum +// +// Make sure plugin base dir exists +func (c *Config) ResolvePluginBaseDir() error { + if c.Plugins == nil { + return nil + } + baseDir := c.Plugins.BaseDir.String() + if baseDir == "" { + baseDir = filepath.Join(c.DataDir, "plugins") + } + if !common.FileExist(baseDir) { + if err := os.MkdirAll(baseDir, 0755); err != nil { + return err + } + } + absBaseDir, err := filepath.Abs(baseDir) + if err != nil { + return err + } + c.Plugins.BaseDir = plugin.EnvironmentAwaredValue(absBaseDir) + return nil +} + +// check if smart-contract-based permissioning is enabled by reading `--permissioned` flag and checking permission config file +func (c *Config) IsPermissionEnabled() bool { + fullPath := filepath.Join(c.DataDir, params.PERMISSION_MODEL_CONFIG) + if _, err := os.Stat(fullPath); err != nil { + log.Warn(fmt.Sprintf("%s file is missing. Smart-contract-based permission service will be disabled", params.PERMISSION_MODEL_CONFIG), "error", err) + return false + } + return true +} + func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { scryptN, scryptP, keydir, err := conf.AccountConfig() var ephemeral string @@ -522,6 +562,12 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { backends = append(backends, schub) } } + if conf.Plugins != nil { + if _, ok := conf.Plugins.Providers[plugin.AccountPluginInterfaceName]; ok { + pluginBackend := pluggable.NewBackend() + backends = append(backends, pluginBackend) + } + } } return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed}, backends...), ephemeral, nil diff --git a/node/config_test.go b/node/config_test.go index 00c24a2391..a6809051a8 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -18,11 +18,21 @@ package node import ( "bytes" + "fmt" "io/ioutil" "os" + "path" "path/filepath" "runtime" "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum/go-ethereum/plugin" + + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/assert" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" @@ -160,3 +170,85 @@ func TestNodeKeyPersistency(t *testing.T) { t.Fatalf("ephemeral node key persisted to disk") } } + +func TestConfig_ResolvePluginBaseDir_whenPluginFeatureIsDisabled(t *testing.T) { + testObject := &Config{} + + assert.NoError(t, testObject.ResolvePluginBaseDir()) +} + +func TestConfig_ResolvePluginBaseDir_whenBaseDirDoesNotExist(t *testing.T) { + arbitraryBaseDir := path.Join(os.TempDir(), fmt.Sprintf("foo-%d", time.Now().Unix())) + defer func() { + _ = os.RemoveAll(arbitraryBaseDir) + }() + testObject := &Config{ + Plugins: &plugin.Settings{ + BaseDir: plugin.EnvironmentAwaredValue(arbitraryBaseDir), + }, + } + + assert.NoError(t, testObject.ResolvePluginBaseDir()) + assert.True(t, common.FileExist(arbitraryBaseDir)) + assert.True(t, path.IsAbs(testObject.Plugins.BaseDir.String())) +} + +func TestConfig_ResolvePluginBaseDir_whenBaseDirExists(t *testing.T) { + arbitraryBaseDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(arbitraryBaseDir) + }() + testObject := &Config{ + Plugins: &plugin.Settings{ + BaseDir: plugin.EnvironmentAwaredValue(arbitraryBaseDir), + }, + } + + assert.NoError(t, testObject.ResolvePluginBaseDir()) + assert.True(t, path.IsAbs(testObject.Plugins.BaseDir.String())) +} + +// Quorum +// +func TestConfig_IsPermissionEnabled_whenTypical(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpdir) + }() + if err := ioutil.WriteFile(path.Join(tmpdir, params.PERMISSION_MODEL_CONFIG), []byte("foo"), 0644); err != nil { + t.Fatal(err) + } + testObject := &Config{ + EnableNodePermission: true, + DataDir: tmpdir, + } + + assert.True(t, testObject.IsPermissionEnabled()) +} + +// Quorum +// +func TestConfig_IsPermissionEnabled_whenPermissionedFlagIsFalse(t *testing.T) { + testObject := &Config{ + EnableNodePermission: false, + } + + assert.False(t, testObject.IsPermissionEnabled()) +} + +// Quorum +// +func TestConfig_IsPermissionEnabled_whenPermissionConfigIsNotAvailable(t *testing.T) { + testObject := &Config{ + EnableNodePermission: true, + DataDir: os.TempDir(), + } + + assert.False(t, testObject.IsPermissionEnabled()) +} diff --git a/node/endpoints.go b/node/endpoints.go index 1baa1b5c41..50364fb5f3 100644 --- a/node/endpoints.go +++ b/node/endpoints.go @@ -17,23 +17,28 @@ package node import ( + "context" + "crypto/tls" + "fmt" "net" "net/http" "time" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/plugin/security" "github.com/ethereum/go-ethereum/rpc" ) // StartHTTPEndpoint starts the HTTP RPC endpoint. -func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) { +func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler, tlsConfigSource security.TLSConfigurationSource) (*http.Server, net.Addr, bool, error) { // start the HTTP listener var ( - listener net.Listener - err error + listener net.Listener + err error + isTlsEnabled bool ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, nil, err + if isTlsEnabled, listener, err = startListener(endpoint, tlsConfigSource); err != nil { + return nil, nil, isTlsEnabled, err } // make sure timeout values are meaningful CheckTimeouts(&timeouts) @@ -43,24 +48,29 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. ReadTimeout: timeouts.ReadTimeout, WriteTimeout: timeouts.WriteTimeout, IdleTimeout: timeouts.IdleTimeout, + + // Ensure to Disable HTTP/2 + // this configuration and customized tls.Config is to follow: https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } go httpSrv.Serve(listener) - return httpSrv, listener.Addr(), err + return httpSrv, listener.Addr(), isTlsEnabled, err } // startWSEndpoint starts a websocket endpoint. -func startWSEndpoint(endpoint string, handler http.Handler) (*http.Server, net.Addr, error) { +func startWSEndpoint(endpoint string, handler http.Handler, tlsConfigSource security.TLSConfigurationSource) (*http.Server, net.Addr, bool, error) { // start the HTTP listener var ( - listener net.Listener - err error + listener net.Listener + err error + isTlsEnabled bool ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, nil, err + if isTlsEnabled, listener, err = startListener(endpoint, tlsConfigSource); err != nil { + return nil, nil, isTlsEnabled, err } wsSrv := &http.Server{Handler: handler} go wsSrv.Serve(listener) - return wsSrv, listener.Addr(), err + return wsSrv, listener.Addr(), isTlsEnabled, err } // checkModuleAvailability checks that all names given in modules are actually @@ -82,6 +92,34 @@ func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available [ return bad, available } +// Quorum +// Produce net.Listener instance with TLS support if tlsConfigSource provides the config +func startListener(endpoint string, tlsConfigSource security.TLSConfigurationSource) (bool, net.Listener, error) { + var tlsConfig *tls.Config + var err error + var listener net.Listener + isTlsEnabled := true + if tlsConfigSource != nil { + if tlsConfig, err = tlsConfigSource.Get(context.Background()); err != nil { + isTlsEnabled = false + } + } else { + isTlsEnabled = false + err = fmt.Errorf("no TLSConfigurationSource found") + } + if isTlsEnabled { + if listener, err = tls.Listen("tcp", endpoint, tlsConfig); err != nil { + return isTlsEnabled, nil, err + } + } else { + log.Info("Security: TLS not enabled", "endpoint", endpoint, "reason", err) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return isTlsEnabled, nil, err + } + } + return isTlsEnabled, listener, nil +} + // CheckTimeouts ensures that timeout values are meaningful func CheckTimeouts(timeouts *rpc.HTTPTimeouts) { if timeouts.ReadTimeout < time.Second { diff --git a/node/node.go b/node/node.go index 329ff425b9..dd1a7d1e87 100644 --- a/node/node.go +++ b/node/node.go @@ -18,6 +18,7 @@ package node import ( "context" + "crypto/ecdsa" "errors" "fmt" "net" @@ -35,6 +36,8 @@ import ( "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/plugin" + "github.com/ethereum/go-ethereum/plugin/security" "github.com/ethereum/go-ethereum/rpc" "github.com/prometheus/tsdb/fileutil" ) @@ -61,17 +64,21 @@ type Node struct { ipcListener net.Listener // IPC RPC listener socket to serve API requests ipcHandler *rpc.Server // IPC RPC request handler to process the API requests + isHttps bool httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled) httpWhitelist []string // HTTP RPC modules to allow through this endpoint httpListenerAddr net.Addr // Address of HTTP RPC listener socket serving API requests httpServer *http.Server // HTTP RPC HTTP server httpHandler *rpc.Server // HTTP RPC request handler to process the API requests + isWss bool wsEndpoint string // WebSocket endpoint (interface + port) to listen at (empty = WebSocket disabled) wsListenerAddr net.Addr // Address of WebSocket RPC listener socket serving API requests wsHTTPServer *http.Server // WebSocket RPC HTTP server wsHandler *rpc.Server // WebSocket RPC request handler to process the API requests + pluginManager *plugin.PluginManager // Manage all plugins for this node. If plugin is not enabled, an EmptyPluginManager is set. + stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -190,11 +197,14 @@ func (n *Node) Start() error { if n.serverConfig.NodeDatabase == "" { n.serverConfig.NodeDatabase = n.config.NodeDB() } + n.serverConfig.EnableNodePermission = n.config.EnableNodePermission + n.serverConfig.DataDir = n.config.DataDir running := &p2p.Server{Config: n.serverConfig} n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) // Otherwise copy and specialize the P2P configuration services := make(map[reflect.Type]Service) + var kinds []reflect.Type for _, constructor := range n.serviceFuncs { // Create a new context for the particular service ctx := &ServiceContext{ @@ -216,6 +226,8 @@ func (n *Node) Start() error { return &DuplicateServiceError{Kind: kind} } services[kind] = service + // to keep track of order in which services are constructed + kinds = append(kinds, kind) } // Gather the protocols and start the freshly assembled P2P server for _, service := range services { @@ -226,7 +238,9 @@ func (n *Node) Start() error { } // Start each of the services var started []reflect.Type - for kind, service := range services { + // start services in the same order that they are constructed + for _, kind := range kinds { + service := services[kind] // Start the next service, stopping all previous upon failure if err := service.Start(running); err != nil { for _, kind := range started { @@ -239,6 +253,12 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup started = append(started, kind) } + // Retrieve PluginManager service if configured + if pm, hasPluginManager := services[reflect.TypeOf(&plugin.PluginManager{})]; hasPluginManager { + n.pluginManager = pm.(*plugin.PluginManager) + } else { + n.pluginManager = plugin.NewEmptyPluginManager() + } // Lastly, start the configured RPC interfaces if err := n.startRPC(services); err != nil { for _, service := range services { @@ -326,7 +346,7 @@ func (n *Node) startInProc(apis []rpc.API) error { n.log.Debug("InProc registered", "namespace", api.Namespace) } n.inprocHandler = handler - return nil + return n.eventmux.Post(rpc.InProcServerReadyEvent{}) } // stopInProc terminates the in-process RPC endpoint. @@ -366,15 +386,51 @@ func (n *Node) stopIPC() { } } +func (n *Node) httpScheme() string { + if n.isHttps { + return "https" + } + return "http" +} + +func (n *Node) wsScheme() string { + if n.isWss { + return "wss" + } + return "ws" +} + +func (n *Node) getSecuritySupports() (tlsConfigSource security.TLSConfigurationSource, authManager security.AuthenticationManager, err error) { + if n.pluginManager.IsEnabled(plugin.SecurityPluginInterfaceName) { + sp := new(plugin.SecurityPluginTemplate) + if err = n.pluginManager.GetPluginTemplate(plugin.SecurityPluginInterfaceName, sp); err != nil { + return + } + if tlsConfigSource, err = sp.TLSConfigurationSource(); err != nil { + return + } + if authManager, err = sp.AuthenticationManager(); err != nil { + return + } + } else { + log.Info("Security Plugin is not enabled") + } + return +} + // startHTTP initializes and starts the HTTP RPC endpoint. func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { // Short circuit if the HTTP endpoint isn't being exposed if endpoint == "" { return nil } + tlsConfigSource, authManager, err := n.getSecuritySupports() + if err != nil { + return err + } // register apis and create handler stack - srv := rpc.NewServer() - err := RegisterApisFromWhitelist(apis, modules, srv, false) + srv := rpc.NewProtectedServer(authManager) + err = RegisterApisFromWhitelist(apis, modules, srv, false) if err != nil { return err } @@ -383,10 +439,11 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors if n.httpEndpoint == n.wsEndpoint { handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) } - httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) + httpServer, addr, isTlsEnabled, err := StartHTTPEndpoint(endpoint, timeouts, handler, tlsConfigSource) if err != nil { return err } + n.isHttps = isTlsEnabled n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) @@ -421,17 +478,21 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig if endpoint == "" { return nil } - - srv := rpc.NewServer() + tlsConfigSource, authManager, err := n.getSecuritySupports() + if err != nil { + return err + } + srv := rpc.NewProtectedServer(authManager) handler := srv.WebsocketHandler(wsOrigins) - err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll) + err = RegisterApisFromWhitelist(apis, modules, srv, exposeAll) if err != nil { return err } - httpServer, addr, err := startWSEndpoint(endpoint, handler) + httpServer, addr, isTlsEnabled, err := startWSEndpoint(endpoint, handler, tlsConfigSource) if err != nil { return err } + n.isWss = isTlsEnabled n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) // All listeners booted successfully n.wsEndpoint = endpoint @@ -585,6 +646,20 @@ func (n *Node) Service(service interface{}) error { return ErrServiceUnknown } +// Quorum +// +// delegate call to node.Config +func (n *Node) IsPermissionEnabled() bool { + return n.config.IsPermissionEnabled() +} + +// Quorum +// +// delegate call to node.Config +func (n *Node) GetNodeKey() *ecdsa.PrivateKey { + return n.config.NodeKey() +} + // DataDir retrieves the current datadir used by the protocol stack. // Deprecated: No files should be stored in this directory, use InstanceDir instead. func (n *Node) DataDir() string { @@ -715,3 +790,13 @@ func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server } return nil } + +// Quorum +// +// This can be used to inspect plugins used in the current node +func (n *Node) PluginManager() *plugin.PluginManager { + n.lock.RLock() + defer n.lock.RUnlock() + + return n.pluginManager +} diff --git a/node/node_test.go b/node/node_test.go index d62194a876..263f5e7af8 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -27,8 +27,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/plugin" "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" ) @@ -635,6 +635,7 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { func startHTTP(t *testing.T) *Node { conf := &Config{HTTPPort: 7453, WSPort: 7453} node, err := New(conf) + node.pluginManager = plugin.NewEmptyPluginManager() if err != nil { t.Error("could not create a new node ", err) } diff --git a/node/service.go b/node/service.go index ef5b995e4b..d3d5ddb42a 100644 --- a/node/service.go +++ b/node/service.go @@ -17,6 +17,7 @@ package node import ( + "crypto/ecdsa" "path/filepath" "reflect" @@ -85,6 +86,11 @@ func (ctx *ServiceContext) Service(service interface{}) error { return ErrServiceUnknown } +// NodeKey returns node key from config +func (ctx *ServiceContext) NodeKey() *ecdsa.PrivateKey { + return ctx.Config.NodeKey() +} + // ExtRPCEnabled returns the indicator whether node enables the external // RPC(http, ws or graphql). func (ctx *ServiceContext) ExtRPCEnabled() bool { diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 3f6cda6d4a..5380f12a9b 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -90,9 +90,10 @@ func (n *Node) Seq() uint64 { return n.r.Seq() } -// Incomplete returns true for nodes with no IP address. +// Quorum +// Incomplete returns true for nodes with no IP address and no hostname if with raftport. func (n *Node) Incomplete() bool { - return n.IP() == nil + return n.IP() == nil && (!n.HasRaftPort() || (n.Host() == "" && n.HasRaftPort())) } // Load retrieves an entry from the underlying record. @@ -100,8 +101,31 @@ func (n *Node) Load(k enr.Entry) error { return n.r.Load(k) } -// IP returns the IP address of the node. This prefers IPv4 addresses. +// IP returns the IP address of the node. +// +// Quorum +// To support DNS lookup in node ip. The function performs hostname lookup if hostname is defined in enr.Hostname +// and falls back to enr.IP value in case of failure. It also makes sure the resolved IP is in IPv4 or IPv6 format func (n *Node) IP() net.IP { + if n.Host() == "" { + // no host is set, so use the IP directly + return n.loadIP() + } + // attempt to look up IP addresses if host is a FQDN + lookupIPs, err := net.LookupIP(n.Host()) + if err != nil { + return n.loadIP() + } + // set to first ip by default & as Ethereum upstream + ip := lookupIPs[0] + // Ensure the IP is 4 bytes long for IPv4 addresses. + if ipv4 := ip.To4(); ipv4 != nil { + ip = ipv4 + } + return ip +} + +func (n *Node) loadIP() net.IP { var ( ip4 enr.IPv4 ip6 enr.IPv6 @@ -115,6 +139,15 @@ func (n *Node) IP() net.IP { return nil } +// Quorum +func (n *Node) Host() string { + var hostname string + n.Load((*enr.Hostname)(&hostname)) + return hostname +} + +// End-Quorum + // UDP returns the UDP port of the node. func (n *Node) UDP() int { var port enr.UDP @@ -122,6 +155,20 @@ func (n *Node) UDP() int { return int(port) } +// used by Quorum RAFT - returns the Raft port of the node +func (n *Node) RaftPort() int { + var port enr.RaftPort + err := n.Load(&port) + if err != nil { + return 0 + } + return int(port) +} + +func (n *Node) HasRaftPort() bool { + return n.RaftPort() > 0 +} + // UDP returns the TCP port of the node. func (n *Node) TCP() int { var port enr.TCP @@ -225,6 +272,34 @@ func (n *ID) UnmarshalText(text []byte) error { return nil } +// ID is a unique identifier for each node used by RAFT +type EnodeID [64]byte + +// ID prints as a long hexadecimal number. +func (n EnodeID) String() string { + return fmt.Sprintf("%x", n[:]) +} + +// The Go syntax representation of a ID is a call to HexID. +func (n EnodeID) GoString() string { + return fmt.Sprintf("enode.HexID(\"%x\")", n[:]) +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (n EnodeID) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(n[:])), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (n *EnodeID) UnmarshalText(text []byte) error { + id, err := RaftHexID(string(text)) + if err != nil { + return err + } + *n = id + return nil +} + // HexID converts a hex string to an ID. // The string may be prefixed with 0x. // It panics if the string is not a valid ID. @@ -236,6 +311,20 @@ func HexID(in string) ID { return id } +// used by Quorum RAFT to derive 64 byte nodeId from 128 byte enodeID +func RaftHexID(in string) (EnodeID, error) { + var id EnodeID + b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) + if err != nil { + return id, err + } else if len(b) != len(id) { + return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) + } + + copy(id[:], b) + return id, nil +} + func ParseID(in string) (ID, error) { var id ID b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index d15859c477..ed3c5b55dd 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -21,6 +21,8 @@ import ( "encoding/hex" "fmt" "math/big" + "net" + "strings" "testing" "testing/quick" @@ -143,3 +145,125 @@ func TestID_logdistEqual(t *testing.T) { t.Errorf("LogDist(x, x) != 0") } } + +// Quorum +// +// test raft port in node detail +func TestNodeInfoForRaftPort(t *testing.T) { + node := NewV4Hostname( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + "192.168.0.1", + 30302, + 30303, + 2021, + ) + wantIP := enr.IPv4{192, 168, 0, 1} + wantUdp := 30303 + wantTcp := 30302 + wantRaftPort := 2021 + assert.Equal(t, wantRaftPort, node.RaftPort(), "node raft port mismatch") + assert.Equal(t, net.IP(wantIP), node.IP(), "node ip mismatch") + assert.Equal(t, wantUdp, node.UDP(), "node UDP port mismatch") + assert.Equal(t, wantTcp, node.TCP(), "node TCP port mismatch") + +} + +// Quorum - test parsing url with hostname (if host is FQDN) +func TestNodeParseUrlWithHostnameForQuorum(t *testing.T) { + var url = "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@localhost:21000?discport=0&raftport=50401" + n, err := ParseV4(url) + if err != nil { + t.Errorf("parsing host failed %v", err) + } + assert.Equal(t, 50401, n.RaftPort()) + + url = "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@localhost1:21000?discport=0&raftport=50401" + _, err = ParseV4(url) + if err != nil { + errMsg := err.Error() + hasError := strings.Contains(errMsg, "no such host") + + assert.Equal(t, true, hasError, err.Error()) + } +} + +// Quorum +// test Incomplete +func TestIncomplete(t *testing.T) { + var testCases = []struct { + n *Node + isIncomplete bool + }{ + { + n: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.IP{127, 0, 0, 1}, + 52150, + 52150, + ), + isIncomplete: false, + }, + { + n: NewV4(hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.ParseIP("::"), + 52150, + 52150, + ), + isIncomplete: false, + }, + { + n: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), + 52150, + 52150, + ), + isIncomplete: false, + }, + { + n: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + nil, + 52150, + 52150, + ), + isIncomplete: true, + }, + { + n: NewV4Hostname( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + "hostname", + 52150, + 52150, + 50400, + ), + isIncomplete: false, + }, + { + n: NewV4Hostname( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + "hostname", + 52150, + 52150, + 0, + ), + isIncomplete: true, + }, + { + n: NewV4Hostname( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + "", + 52150, + 52150, + 50400, + ), + isIncomplete: true, + }, + } + + for i, test := range testCases { + if test.n.Incomplete() != test.isIncomplete { + t.Errorf("test %d: Node.Incomplete() mismatch:\ngot: %v\nwant: %v", i, test.n.Incomplete(), test.isIncomplete) + } + } +} diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go index c445049102..8ed500c566 100644 --- a/p2p/enode/urlv4.go +++ b/p2p/enode/urlv4.go @@ -87,6 +87,12 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node { if len(ip) > 0 { r.Set(enr.IP(ip)) } + return newV4(pubkey, r, tcp, udp) +} + +// broken out from `func NewV4` (above) same in upstream go-ethereum, but taken out +// to avoid code duplication b/t NewV4 and NewV4Hostname +func newV4(pubkey *ecdsa.PublicKey, r enr.Record, tcp, udp int) *Node { if udp != 0 { r.Set(enr.UDP(udp)) } @@ -107,9 +113,33 @@ func isNewV4(n *Node) bool { return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0 } +// Quorum + +// NewV4Hostname creates a node from discovery v4 node information. The record +// contained in the node has a zero-length signature. It sets the hostname or ip +// of the node depends on hostname context +func NewV4Hostname(pubkey *ecdsa.PublicKey, hostname string, tcp, udp, raftPort int) *Node { + var r enr.Record + + if ip := net.ParseIP(hostname); ip == nil { + r.Set(enr.Hostname(hostname)) + } else { + r.Set(enr.IP(ip)) + } + + if raftPort != 0 { + r.Set(enr.RaftPort(raftPort)) + } + + return newV4(pubkey, r, tcp, udp) +} + +// End-Quorum + func parseComplete(rawurl string) (*Node, error) { var ( id *ecdsa.PublicKey + ip net.IP tcpPort, udpPort uint64 ) u, err := url.Parse(rawurl) @@ -126,34 +156,58 @@ func parseComplete(rawurl string) (*Node, error) { if id, err = parsePubkey(u.User.String()); err != nil { return nil, fmt.Errorf("invalid public key (%v)", err) } + qv := u.Query() // Parse the IP address. - ip := net.ParseIP(u.Hostname()) - if ip == nil { - ips, err := lookupIPFunc(u.Hostname()) - if err != nil { + ips, err := net.LookupIP(u.Hostname()) + if err != nil { + // Quorum: if IP look up fail don't return error for raft url + if qv.Get("raftport") == "" { return nil, err } + } else { ip = ips[0] - } - // Ensure the IP is 4 bytes long for IPv4 addresses. - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 + // Ensure the IP is 4 bytes long for IPv4 addresses. + if ipv4 := ip.To4(); ipv4 != nil { + ip = ipv4 + } } // Parse the port numbers. if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil { return nil, errors.New("invalid port") } udpPort = tcpPort - qv := u.Query() + if qv.Get("discport") != "" { udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16) if err != nil { return nil, errors.New("invalid discport in query") } } + + // Quorum + if qv.Get("raftport") != "" { + raftPort, err := strconv.ParseUint(qv.Get("raftport"), 10, 16) + if err != nil { + return nil, errors.New("invalid raftport in query") + } + if u.Hostname() == "" { + return nil, errors.New("empty hostname in raft url") + } + return NewV4Hostname(id, u.Hostname(), int(tcpPort), int(udpPort), int(raftPort)), nil + } + // End-Quorum + return NewV4(id, ip, int(tcpPort), int(udpPort)), nil } +func HexPubkey(h string) (*ecdsa.PublicKey, error) { + k, err := parsePubkey(h) + if err != nil { + return nil, err + } + return k, err +} + // parsePubkey parses a hex-encoded secp256k1 public key. func parsePubkey(in string) (*ecdsa.PublicKey, error) { b, err := hex.DecodeString(in) @@ -166,6 +220,24 @@ func parsePubkey(in string) (*ecdsa.PublicKey, error) { return crypto.UnmarshalPubkey(b) } +// used by Quorum RAFT - to derive enodeID +func (n *Node) EnodeID() string { + var ( + scheme enr.ID + nodeid string + key ecdsa.PublicKey + ) + n.Load(&scheme) + n.Load((*Secp256k1)(&key)) + switch { + case scheme == "v4" || key != ecdsa.PublicKey{}: + nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:]) + default: + nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) + } + return nodeid +} + func (n *Node) URLv4() string { var ( scheme enr.ID @@ -184,12 +256,26 @@ func (n *Node) URLv4() string { if n.Incomplete() { u.Host = nodeid } else { - addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} u.User = url.User(nodeid) - u.Host = addr.String() + if n.Host() != "" && net.ParseIP(n.Host()) == nil { + // Quorum + u.Host = net.JoinHostPort(n.Host(), strconv.Itoa(n.TCP())) + } else { + addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} + u.Host = addr.String() + } if n.UDP() != n.TCP() { u.RawQuery = "discport=" + strconv.Itoa(n.UDP()) } + // Quorum + if n.HasRaftPort() { + raftQuery := "raftport=" + strconv.Itoa(n.RaftPort()) + if len(u.RawQuery) > 0 { + u.RawQuery = u.RawQuery + "&" + raftQuery + } else { + u.RawQuery = raftQuery + } + } } return u.String() } diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go index 33de96cc57..57ba91cbd1 100644 --- a/p2p/enode/urlv4_test.go +++ b/p2p/enode/urlv4_test.go @@ -155,6 +155,21 @@ var parseNodeTests = []struct { input: "://foo", wantError: errMissingPrefix.Error(), }, + { + // Quorum: raft url with invalid hostname (no error, hostname will be saved) + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3?raftport=50401", + wantResult: NewV4Hostname(hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), "hostname", 3, 3, 50401), + }, + { + // Quorum: raft url with valid hostname + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@localhost:3?raftport=50401", + wantResult: NewV4Hostname(hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), "localhost", 3, 3, 50401), + }, + { + // Quorum: raft url with no hostname + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@:3?raftport=50401", + wantError: `empty hostname in raft url`, + }, } func hexPubkey(h string) *ecdsa.PublicKey { diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go index f2118401af..9c54da662f 100644 --- a/p2p/enr/entries.go +++ b/p2p/enr/entries.go @@ -94,6 +94,16 @@ func (v IP) ENRKey() string { return "ip" } +// Quorum +// RaftPort is the "raftport" key, which holds the raftport of the node +type RaftPort uint16 + +func (v RaftPort) ENRKey() string { return "raftport" } + +type Hostname string + +func (v Hostname) ENRKey() string { return "hostname" } + // EncodeRLP implements rlp.Encoder. func (v IP) EncodeRLP(w io.Writer) error { if ip4 := net.IP(v).To4(); ip4 != nil { diff --git a/p2p/peer.go b/p2p/peer.go index 54fb653e24..c6f8fec14f 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -116,6 +116,10 @@ type Peer struct { // events receives message send / receive events if set events *event.Feed + + // Quorum + EthPeerRegistered chan struct{} + EthPeerDisconnected chan struct{} } // NewPeer returns a peer for testing purposes. @@ -166,6 +170,14 @@ func (p *Peer) Disconnect(reason DiscReason) { case p.disc <- reason: case <-p.closed: } + + // Quorum + // if a quorum eth service subprotocol is waiting on EthPeerRegistered, notify the peer that it was not registered. + select { + case p.EthPeerDisconnected <- struct{}{}: + default: + } + // Quorum } // String implements fmt.Stringer. @@ -189,6 +201,9 @@ func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer { protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop closed: make(chan struct{}), log: log.New("id", conn.node.ID(), "conn", conn.flags), + // Quorum + EthPeerRegistered: make(chan struct{}, 1), + EthPeerDisconnected: make(chan struct{}, 1), } return p } diff --git a/p2p/peer_error.go b/p2p/peer_error.go index ab61bfef06..f44c5b4c50 100644 --- a/p2p/peer_error.go +++ b/p2p/peer_error.go @@ -26,9 +26,22 @@ const ( errInvalidMsg ) +// Quorum +// +// Constants for peer connection errors +const ( + // When permissioning is enabled, and node is not permissioned in the network + errPermissionDenied = iota + 100 + // Unauthorized node joining existing raft cluster + errNotInRaftCluster +) + var errorToString = map[int]string{ errInvalidMsgCode: "invalid message code", errInvalidMsg: "invalid message", + // Quorum + errPermissionDenied: "permission denied", + errNotInRaftCluster: "not in raft cluster", } type peerError struct { diff --git a/p2p/server.go b/p2p/server.go index 1fe5f39789..a417f1d936 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -40,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" + "github.com/ethereum/go-ethereum/permission/core" ) const ( @@ -153,6 +154,9 @@ type Config struct { // whenever a message is sent to or received from a peer EnableMsgEvents bool + EnableNodePermission bool `toml:",omitempty"` + + DataDir string `toml:",omitempty"` // Logger is a custom logger to use with the p2p.Server. Logger log.Logger `toml:",omitempty"` @@ -198,6 +202,12 @@ type Server struct { // State of run loop and listenLoop. inboundHistory expHeap + + // raft peers info + checkPeerInRaft func(*enode.Node) bool + + // permissions - check if node is permissioned + isNodePermissionedFunc func(node *enode.Node, nodename string, currentNode string, datadir string, direction string) bool } type peerOpFunc func(map[enode.ID]*Peer) @@ -948,6 +958,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } + if dialDest != nil { // For dialed connections, check that the remote public key matches. if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 { @@ -958,6 +969,52 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro c.node = nodeFromConn(remotePubkey, c.fd) } clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags) + + // If raft is running, check if the dialing node is in the raft cluster + // Node doesn't belong to raft cluster is not allowed to join the p2p network + if srv.checkPeerInRaft != nil && !srv.checkPeerInRaft(c.node) { + node := c.node.ID().String() + log.Trace("incoming connection peer is not in the raft cluster", "enode.id", node) + return newPeerError(errNotInRaftCluster, "id=%s…%s", node[:4], node[len(node)-4:]) + } + + //START - QUORUM Permissioning + currentNode := srv.NodeInfo().ID + cnodeName := srv.NodeInfo().Name + clog.Trace("Quorum permissioning", + "EnableNodePermission", srv.EnableNodePermission, + "DataDir", srv.DataDir, + "Current Node ID", currentNode, + "Node Name", cnodeName, + "Dialed Dest", dialDest, + "Connection ID", c.node.ID(), + "Connection String", c.node.ID().String()) + + if srv.EnableNodePermission { + clog.Trace("Node Permissioning is Enabled.") + nodeId := c.node.ID().String() + node := c.node + direction := "INCOMING" + if dialDest != nil { + node = dialDest + nodeId = dialDest.ID().String() + direction = "OUTGOING" + log.Trace("Node Permissioning", "Connection Direction", direction) + } + + if srv.isNodePermissionedFunc == nil { + if !core.IsNodePermissioned(nodeId, currentNode, srv.DataDir, direction) { + return newPeerError(errPermissionDenied, "id=%s…%s %s id=%s…%s", currentNode[:4], currentNode[len(currentNode)-4:], direction, nodeId[:4], nodeId[len(nodeId)-4:]) + } + } else if !srv.isNodePermissionedFunc(node, nodeId, currentNode, srv.DataDir, direction) { + return newPeerError(errPermissionDenied, "id=%s…%s %s id=%s…%s", currentNode[:4], currentNode[len(currentNode)-4:], direction, nodeId[:4], nodeId[len(nodeId)-4:]) + } + } else { + clog.Trace("Node Permissioning is Disabled.") + } + + //END - QUORUM Permissioning + err = srv.checkpoint(c, srv.checkpointPostHandshake) if err != nil { clog.Trace("Rejected peer", "err", err) @@ -1119,3 +1176,13 @@ func (srv *Server) PeersInfo() []*PeerInfo { } return infos } + +func (srv *Server) SetCheckPeerInRaft(f func(*enode.Node) bool) { + srv.checkPeerInRaft = f +} + +func (srv *Server) SetIsNodePermissioned(f func(*enode.Node, string, string, string, string) bool) { + if srv.isNodePermissionedFunc == nil { + srv.isNodePermissionedFunc = f + } +} diff --git a/p2p/server_test.go b/p2p/server_test.go index 7dc344a67d..dce00a5092 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -20,8 +20,11 @@ import ( "crypto/ecdsa" "errors" "io" + "io/ioutil" "math/rand" "net" + "os" + "path" "reflect" "testing" "time" @@ -31,6 +34,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/assert" "golang.org/x/crypto/sha3" ) @@ -458,6 +463,74 @@ func TestServerSetupConn(t *testing.T) { } } +func TestServerSetupConn_whenNotInRaftCluster(t *testing.T) { + var ( + clientkey, srvkey = newkey(), newkey() + clientpub = &clientkey.PublicKey + ) + + clientNode := enode.NewV4(clientpub, nil, 0, 0) + srv := &Server{ + Config: Config{ + PrivateKey: srvkey, + NoDiscovery: true, + }, + newTransport: func(fd net.Conn) transport { return newTestTransport(clientpub, fd) }, + log: log.New(), + checkPeerInRaft: func(node *enode.Node) bool { + return false + }, + } + if err := srv.Start(); err != nil { + t.Fatalf("couldn't start server: %v", err) + } + defer srv.Stop() + p1, _ := net.Pipe() + err := srv.SetupConn(p1, inboundConn, clientNode) + + assert.IsType(t, &peerError{}, err) + perr := err.(*peerError) + t.Log(perr.Error()) + assert.Equal(t, errNotInRaftCluster, perr.code) +} + +func TestServerSetupConn_whenNotPermissioned(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer func() { _ = os.RemoveAll(tmpDir) }() + if err := ioutil.WriteFile(path.Join(tmpDir, params.PERMISSIONED_CONFIG), []byte("[]"), 0644); err != nil { + t.Fatal(err) + } + var ( + clientkey, srvkey = newkey(), newkey() + clientpub = &clientkey.PublicKey + ) + clientNode := enode.NewV4(clientpub, nil, 0, 0) + srv := &Server{ + Config: Config{ + PrivateKey: srvkey, + NoDiscovery: true, + DataDir: tmpDir, + EnableNodePermission: true, + }, + newTransport: func(fd net.Conn) transport { return newTestTransport(clientpub, fd) }, + log: log.New(), + } + if err := srv.Start(); err != nil { + t.Fatalf("couldn't start server: %v", err) + } + defer srv.Stop() + p1, _ := net.Pipe() + err = srv.SetupConn(p1, inboundConn, clientNode) + + assert.IsType(t, &peerError{}, err) + perr := err.(*peerError) + t.Log(perr.Error()) + assert.Equal(t, errPermissionDenied, perr.code) +} + type setupTransport struct { pubkey *ecdsa.PublicKey encHandshakeErr error diff --git a/params/config.go b/params/config.go index ddbe2ae8d1..89719b9d99 100644 --- a/params/config.go +++ b/params/config.go @@ -18,6 +18,7 @@ package params import ( "encoding/binary" + "errors" "fmt" "math/big" @@ -239,17 +240,19 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, false, 32, 35, big.NewInt(0), big.NewInt(0), nil, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil} TestRules = TestChainConfig.Rules(new(big.Int)) + + QuorumTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0)} ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and @@ -294,6 +297,11 @@ type CheckpointOracleConfig struct { Threshold uint64 `json:"threshold"` } +type MaxCodeConfigStruct struct { + Block *big.Int `json:"block,omitempty"` + Size uint64 `json:"size,omitempty"` +} + // ChainConfig is the core config which determines the blockchain settings. // // ChainConfig is stored in the database on a per block basis. This means @@ -324,8 +332,22 @@ type ChainConfig struct { EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) // Various consensus engines - Ethash *EthashConfig `json:"ethash,omitempty"` - Clique *CliqueConfig `json:"clique,omitempty"` + Ethash *EthashConfig `json:"ethash,omitempty"` + Clique *CliqueConfig `json:"clique,omitempty"` + Istanbul *IstanbulConfig `json:"istanbul,omitempty"` + + IsQuorum bool `json:"isQuorum"` // Quorum flag + TransactionSizeLimit uint64 `json:"txnSizeLimit"` // Quorum - transaction size limit + MaxCodeSize uint64 `json:"maxCodeSize"` // Quorum - maximum CodeSize of contract + // Quorum + // + // QIP714Block implements the permissions related changes + QIP714Block *big.Int `json:"qip714Block,omitempty"` + MaxCodeSizeChangeBlock *big.Int `json:"maxCodeSizeChangeBlock,omitempty"` + // to track multiple changes to maxCodeSize + MaxCodeSizeConfig []MaxCodeConfigStruct `json:"maxCodeSizeConfig,omitempty"` + // Quorum + PrivacyEnhancementsBlock *big.Int `json:"privacyEnhancementsBlock,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -338,8 +360,9 @@ func (c *EthashConfig) String() string { // CliqueConfig is the consensus engine configs for proof-of-authority based sealing. type CliqueConfig struct { - Period uint64 `json:"period"` // Number of seconds between blocks to enforce - Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint + AllowedFutureBlockTime uint64 `json:"allowedFutureBlockTime"` // Max time (in seconds) from current time allowed for blocks, before they're considered future blocks } // String implements the stringer interface, returning the consensus engine details. @@ -347,6 +370,18 @@ func (c *CliqueConfig) String() string { return "clique" } +// IstanbulConfig is the consensus engine configs for Istanbul based sealing. +type IstanbulConfig struct { + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint + ProposerPolicy uint64 `json:"policy"` // The policy for proposer selection + Ceil2Nby3Block *big.Int `json:"ceil2Nby3Block,omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)] +} + +// String implements the stringer interface, returning the consensus engine details. +func (c *IstanbulConfig) String() string { + return "istanbul" +} + // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { var engine interface{} @@ -355,10 +390,12 @@ func (c *ChainConfig) String() string { engine = c.Ethash case c.Clique != nil: engine = c.Clique + case c.Istanbul != nil: + engine = c.Istanbul default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v IsQuorum: %v Constantinople: %v TransactionSizeLimit: %v MaxCodeSize: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v YOLO v1: %v PrivacyEnhancements: %v Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -367,15 +404,33 @@ func (c *ChainConfig) String() string { c.EIP155Block, c.EIP158Block, c.ByzantiumBlock, + c.IsQuorum, c.ConstantinopleBlock, + c.TransactionSizeLimit, + c.MaxCodeSize, c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, c.YoloV1Block, + c.PrivacyEnhancementsBlock, engine, ) } +// Quorum - validate code size and transaction size limit +func (c *ChainConfig) IsValid() error { + + if c.TransactionSizeLimit < 32 || c.TransactionSizeLimit > 128 { + return errors.New("Genesis transaction size limit must be between 32 and 128") + } + + if c.MaxCodeSize != 0 && (c.MaxCodeSize < 24 || c.MaxCodeSize > 128) { + return errors.New("Genesis max code size must be between 24 and 128") + } + + return nil +} + // IsHomestead returns whether num is either equal to the homestead block or greater. func (c *ChainConfig) IsHomestead(num *big.Int) bool { return isForked(c.HomesteadBlock, num) @@ -438,15 +493,149 @@ func (c *ChainConfig) IsEWASM(num *big.Int) bool { return isForked(c.EWASMBlock, num) } +// Quorum +// +// IsQIP714 returns whether num represents a block number where permissions is enabled +func (c *ChainConfig) IsQIP714(num *big.Int) bool { + return isForked(c.QIP714Block, num) +} + +// IsMaxCodeSizeChangeBlock returns whether num represents a block number +// where maxCodeSize change was done +func (c *ChainConfig) IsMaxCodeSizeChangeBlock(num *big.Int) bool { + return isForked(c.MaxCodeSizeChangeBlock, num) +} + +// Quorum +// +// GetMaxCodeSize returns maxCodeSize for the given block number +func (c *ChainConfig) GetMaxCodeSize(num *big.Int) int { + maxCodeSize := MaxCodeSize + + if len(c.MaxCodeSizeConfig) > 0 { + for _, data := range c.MaxCodeSizeConfig { + if data.Block.Cmp(num) > 0 { + break + } + maxCodeSize = int(data.Size) * 1024 + } + } else if c.MaxCodeSize > 0 { + if c.MaxCodeSizeChangeBlock != nil && c.MaxCodeSizeChangeBlock.Cmp(big.NewInt(0)) >= 0 { + if c.IsMaxCodeSizeChangeBlock(num) { + maxCodeSize = int(c.MaxCodeSize) * 1024 + } + } else { + maxCodeSize = int(c.MaxCodeSize) * 1024 + } + } + return maxCodeSize +} + +// validates the maxCodeSizeConfig data passed in config +func (c *ChainConfig) CheckMaxCodeConfigData() error { + if c.MaxCodeSize != 0 || (c.MaxCodeSizeChangeBlock != nil && c.MaxCodeSizeChangeBlock.Cmp(big.NewInt(0)) >= 0) { + return errors.New("maxCodeSize & maxCodeSizeChangeBlock deprecated. Consider using maxCodeSizeConfig") + } + // validate max code size data + // 1. Code size should not be less than 24 and greater than 128 + // 2. block entries are in ascending order + prevBlock := big.NewInt(0) + for _, data := range c.MaxCodeSizeConfig { + if data.Size < 24 || data.Size > 128 { + return errors.New("Genesis max code size must be between 24 and 128") + } + if data.Block == nil { + return errors.New("Block number not given in maxCodeSizeConfig data") + } + if data.Block.Cmp(prevBlock) < 0 { + return errors.New("invalid maxCodeSize detail, block order has to be ascending") + } + prevBlock = data.Block + } + + return nil +} + +// checks if changes to maxCodeSizeConfig proposed are compatible +// with already existing genesis data +func isMaxCodeSizeConfigCompatible(c1, c2 *ChainConfig, head *big.Int) (error, *big.Int, *big.Int) { + if len(c1.MaxCodeSizeConfig) == 0 && len(c2.MaxCodeSizeConfig) == 0 { + // maxCodeSizeConfig not used. return + return nil, big.NewInt(0), big.NewInt(0) + } + + // existing config had maxCodeSizeConfig and new one does not have the same return error + if len(c1.MaxCodeSizeConfig) > 0 && len(c2.MaxCodeSizeConfig) == 0 { + return fmt.Errorf("genesis file missing max code size information"), head, head + } + + if len(c2.MaxCodeSizeConfig) > 0 && len(c1.MaxCodeSizeConfig) == 0 { + return nil, big.NewInt(0), big.NewInt(0) + } + + // check the number of records below current head in both configs + // if they do not match throw an error + c1RecsBelowHead := 0 + for _, data := range c1.MaxCodeSizeConfig { + if data.Block.Cmp(head) <= 0 { + c1RecsBelowHead++ + } else { + break + } + } + + c2RecsBelowHead := 0 + for _, data := range c2.MaxCodeSizeConfig { + if data.Block.Cmp(head) <= 0 { + c2RecsBelowHead++ + } else { + break + } + } + + // if the count of past records is not matching return error + if c1RecsBelowHead != c2RecsBelowHead { + return errors.New("maxCodeSizeConfig data incompatible. updating maxCodeSize for past"), head, head + } + + // validate that each past record is matching exactly. if not return error + for i := 0; i < c1RecsBelowHead; i++ { + if c1.MaxCodeSizeConfig[i].Block.Cmp(c2.MaxCodeSizeConfig[i].Block) != 0 || + c1.MaxCodeSizeConfig[i].Size != c2.MaxCodeSizeConfig[i].Size { + return errors.New("maxCodeSizeConfig data incompatible. maxCodeSize historical data does not match"), head, head + } + } + + return nil, big.NewInt(0), big.NewInt(0) +} + +// IsPrivacyEnhancementsEnabled returns whether num represents a block number after the PrivacyEnhancementsEnabled fork +func (c *ChainConfig) IsPrivacyEnhancementsEnabled(num *big.Int) bool { + return isForked(c.PrivacyEnhancementsBlock, num) +} + +// /Quorum + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. -func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError { +func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, isQuorumEIP155Activated bool) *ConfigCompatError { bhead := new(big.Int).SetUint64(height) + // check if the maxCodesize data passed is compatible 1st + // this is being handled separately as it can have breaks + // at multiple block heights and cannot be handled with in + // checkCompatible + + // compare the maxCodeSize data between the old and new config + err, cBlock, newCfgBlock := isMaxCodeSizeConfigCompatible(c, newcfg, bhead) + if err != nil { + return newCompatError(err.Error(), cBlock, newCfgBlock) + } + // Iterate checkCompatible to find the lowest conflict. var lasterr *ConfigCompatError for { - err := c.checkCompatible(newcfg, bhead) + err := c.checkCompatible(newcfg, bhead, isQuorumEIP155Activated) if err == nil || (lasterr != nil && err.RewindTo == lasterr.RewindTo) { break } @@ -457,7 +646,7 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *Confi } // CheckConfigForkOrder checks that we don't "skip" any forks, geth isn't pluggable enough -// to guarantee that forks can be implemented in a different order than on official networks +// to guarantee that forks func (c *ChainConfig) CheckConfigForkOrder() error { type fork struct { name string @@ -499,7 +688,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { return nil } -func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *ConfigCompatError { +func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int, isQuorumEIP155Activated bool) *ConfigCompatError { if isForkIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, head) { return newCompatError("Homestead fork block", c.HomesteadBlock, newcfg.HomesteadBlock) } @@ -512,9 +701,12 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.EIP150Block, newcfg.EIP150Block, head) { return newCompatError("EIP150 fork block", c.EIP150Block, newcfg.EIP150Block) } - if isForkIncompatible(c.EIP155Block, newcfg.EIP155Block, head) { + if isQuorumEIP155Activated && c.ChainID != nil && isForkIncompatible(c.EIP155Block, newcfg.EIP155Block, head) { return newCompatError("EIP155 fork block", c.EIP155Block, newcfg.EIP155Block) } + if isQuorumEIP155Activated && c.ChainID != nil && c.IsEIP155(head) && !configNumEqual(c.ChainID, newcfg.ChainID) { + return newCompatError("EIP155 chain ID", c.ChainID, newcfg.ChainID) + } if isForkIncompatible(c.EIP158Block, newcfg.EIP158Block, head) { return newCompatError("EIP158 fork block", c.EIP158Block, newcfg.EIP158Block) } @@ -542,6 +734,18 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) } + if c.Istanbul != nil && newcfg.Istanbul != nil && isForkIncompatible(c.Istanbul.Ceil2Nby3Block, newcfg.Istanbul.Ceil2Nby3Block, head) { + return newCompatError("Ceil 2N/3 fork block", c.Istanbul.Ceil2Nby3Block, newcfg.Istanbul.Ceil2Nby3Block) + } + if isForkIncompatible(c.QIP714Block, newcfg.QIP714Block, head) { + return newCompatError("permissions fork block", c.QIP714Block, newcfg.QIP714Block) + } + if newcfg.MaxCodeSizeChangeBlock != nil && isForkIncompatible(c.MaxCodeSizeChangeBlock, newcfg.MaxCodeSizeChangeBlock, head) { + return newCompatError("max code size change fork block", c.MaxCodeSizeChangeBlock, newcfg.MaxCodeSizeChangeBlock) + } + if isForkIncompatible(c.PrivacyEnhancementsBlock, newcfg.PrivacyEnhancementsBlock, head) { + return newCompatError("Privacy Enhancements fork block", c.PrivacyEnhancementsBlock, newcfg.PrivacyEnhancementsBlock) + } return nil } @@ -610,6 +814,7 @@ type Rules struct { IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsYoloV1 bool + IsPrivacyEnhancementsEnabled bool } // Rules ensures c's ChainID is not nil. @@ -619,15 +824,16 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { chainID = new(big.Int) } return Rules{ - ChainID: new(big.Int).Set(chainID), - IsHomestead: c.IsHomestead(num), - IsEIP150: c.IsEIP150(num), - IsEIP155: c.IsEIP155(num), - IsEIP158: c.IsEIP158(num), - IsByzantium: c.IsByzantium(num), - IsConstantinople: c.IsConstantinople(num), - IsPetersburg: c.IsPetersburg(num), - IsIstanbul: c.IsIstanbul(num), - IsYoloV1: c.IsYoloV1(num), + ChainID: new(big.Int).Set(chainID), + IsHomestead: c.IsHomestead(num), + IsEIP150: c.IsEIP150(num), + IsEIP155: c.IsEIP155(num), + IsEIP158: c.IsEIP158(num), + IsByzantium: c.IsByzantium(num), + IsConstantinople: c.IsConstantinople(num), + IsPetersburg: c.IsPetersburg(num), + IsIstanbul: c.IsIstanbul(num), + IsYoloV1: c.IsYoloV1(num), + IsPrivacyEnhancementsEnabled: c.IsPrivacyEnhancementsEnabled(num), } } diff --git a/params/config_test.go b/params/config_test.go index 02c5fe2917..8ebd08d77e 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -22,12 +22,100 @@ import ( "testing" ) +// Quorum - test code size and transaction size limit in chain config +func TestMaxCodeSizeAndTransactionSizeLimit(t *testing.T) { + type testData struct { + size uint64 + valid bool + err string + } + type testDataType struct { + isCodeSize bool + data []testData + } + + const codeSizeErr = "Genesis max code size must be between 24 and 128" + const txSizeErr = "Genesis transaction size limit must be between 32 and 128" + var codeSizeData = []testData{ + {23, false, codeSizeErr}, + {24, true, ""}, + {50, true, ""}, + {128, true, ""}, + {129, false, codeSizeErr}, + } + + var txSizeData = []testData{ + {31, false, txSizeErr}, + {32, true, ""}, + {50, true, ""}, + {128, true, ""}, + {129, false, txSizeErr}, + } + + var testDataArr = []testDataType{ + {true, codeSizeData}, + {false, txSizeData}, + } + + for _, td := range testDataArr { + var ccfg *ChainConfig + for _, d := range td.data { + var msgPrefix string + if td.isCodeSize { + ccfg = &ChainConfig{MaxCodeSize: d.size, TransactionSizeLimit: 50} + msgPrefix = "max code size" + } else { + ccfg = &ChainConfig{MaxCodeSize: 50, TransactionSizeLimit: d.size} + msgPrefix = "transaction size limit" + } + err := ccfg.IsValid() + if d.valid { + if err != nil { + t.Errorf(msgPrefix+" %d, expected no error but got %v", d.size, err) + } + } else { + if err == nil { + t.Errorf(msgPrefix+" %d, expected error but got none", d.size) + } else { + if err.Error() != d.err { + t.Errorf(msgPrefix+" %d, expected error but got %v", d.size, err.Error()) + } + } + } + } + } +} + func TestCheckCompatible(t *testing.T) { type test struct { stored, new *ChainConfig head uint64 wantErr *ConfigCompatError } + var storedMaxCodeConfig0, storedMaxCodeConfig1, storedMaxCodeConfig2 []MaxCodeConfigStruct + defaultRec := MaxCodeConfigStruct{big.NewInt(0), 24} + rec1 := MaxCodeConfigStruct{big.NewInt(5), 32} + rec2 := MaxCodeConfigStruct{big.NewInt(10), 40} + rec3 := MaxCodeConfigStruct{big.NewInt(8), 40} + + storedMaxCodeConfig0 = append(storedMaxCodeConfig0, defaultRec) + + storedMaxCodeConfig1 = append(storedMaxCodeConfig1, defaultRec) + storedMaxCodeConfig1 = append(storedMaxCodeConfig1, rec1) + storedMaxCodeConfig1 = append(storedMaxCodeConfig1, rec2) + + storedMaxCodeConfig2 = append(storedMaxCodeConfig2, rec1) + storedMaxCodeConfig2 = append(storedMaxCodeConfig2, rec2) + + var passedValidMaxConfig0 []MaxCodeConfigStruct + passedValidMaxConfig0 = append(passedValidMaxConfig0, defaultRec) + passedValidMaxConfig0 = append(passedValidMaxConfig0, rec1) + + var passedValidMaxConfig1 []MaxCodeConfigStruct + passedValidMaxConfig1 = append(passedValidMaxConfig1, defaultRec) + passedValidMaxConfig1 = append(passedValidMaxConfig1, rec1) + passedValidMaxConfig1 = append(passedValidMaxConfig1, rec3) + tests := []test{ {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 0, wantErr: nil}, {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 100, wantErr: nil}, @@ -70,10 +158,130 @@ func TestCheckCompatible(t *testing.T) { RewindTo: 9, }, }, + { + stored: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(10)}}, + new: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(20)}}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(10)}}, + new: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(20)}}, + head: 30, + wantErr: &ConfigCompatError{ + What: "Ceil 2N/3 fork block", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(20), + RewindTo: 9, + }, + }, + { + stored: &ChainConfig{MaxCodeSizeChangeBlock: big.NewInt(10)}, + new: &ChainConfig{MaxCodeSizeChangeBlock: big.NewInt(20)}, + head: 30, + wantErr: &ConfigCompatError{ + What: "max code size change fork block", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(20), + RewindTo: 9, + }, + }, + { + stored: &ChainConfig{MaxCodeSizeChangeBlock: big.NewInt(10)}, + new: &ChainConfig{MaxCodeSizeChangeBlock: big.NewInt(20)}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{QIP714Block: big.NewInt(10)}, + new: &ChainConfig{QIP714Block: big.NewInt(20)}, + head: 30, + wantErr: &ConfigCompatError{ + What: "permissions fork block", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(20), + RewindTo: 9, + }, + }, + { + stored: &ChainConfig{QIP714Block: big.NewInt(10)}, + new: &ChainConfig{QIP714Block: big.NewInt(20)}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, + new: &ChainConfig{MaxCodeSizeConfig: nil}, + head: 4, + wantErr: &ConfigCompatError{ + What: "genesis file missing max code size information", + StoredConfig: big.NewInt(4), + NewConfig: big.NewInt(4), + RewindTo: 3, + }, + }, + { + stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, + new: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, + new: &ChainConfig{MaxCodeSizeConfig: passedValidMaxConfig0}, + head: 10, + wantErr: &ConfigCompatError{ + What: "maxCodeSizeConfig data incompatible. updating maxCodeSize for past", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(10), + RewindTo: 9, + }, + }, + { + stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, + new: &ChainConfig{MaxCodeSizeConfig: passedValidMaxConfig0}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig1}, + new: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig1}, + head: 12, + wantErr: nil, + }, + { + stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig1}, + new: &ChainConfig{MaxCodeSizeConfig: passedValidMaxConfig1}, + head: 12, + wantErr: &ConfigCompatError{ + What: "maxCodeSizeConfig data incompatible. maxCodeSize historical data does not match", + StoredConfig: big.NewInt(12), + NewConfig: big.NewInt(12), + RewindTo: 11, + }, + }, + { + stored: &ChainConfig{MaxCodeSize: 32}, + new: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig2}, + head: 8, + wantErr: nil, + }, + { + stored: &ChainConfig{MaxCodeSize: 32}, + new: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig2}, + head: 15, + wantErr: nil, + }, + { + stored: &ChainConfig{MaxCodeSize: 32, MaxCodeSizeChangeBlock: big.NewInt(10)}, + new: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig1}, + head: 15, + wantErr: nil, + }, } for _, test := range tests { - err := test.stored.CheckCompatible(test.new, test.head) + err := test.stored.CheckCompatible(test.new, test.head, false) if !reflect.DeepEqual(err, test.wantErr) { t.Errorf("error mismatch:\nstored: %v\nnew: %v\nhead: %v\nerr: %v\nwant: %v", test.stored, test.new, test.head, err, test.wantErr) } diff --git a/params/network_params.go b/params/network_params.go index 9311b5e2d5..ce943f3855 100644 --- a/params/network_params.go +++ b/params/network_params.go @@ -65,3 +65,23 @@ const ( // reorgs, by the light pruner as the pruning validity guarantee. LightImmutabilityThreshold = 30000 ) + +// //Quorum +var quorumImmutabilityThreshold int + +// returns the immutability threshold set for the network +func GetImmutabilityThreshold() int { + + if quorumImmutabilityThreshold > 0 { + return quorumImmutabilityThreshold + } + + return FullImmutabilityThreshold +} + +// sets the immutability threshold and isQuorum to passed values +func SetQuorumImmutabilityThreshold(immutabilityThreshold int) { + quorumImmutabilityThreshold = immutabilityThreshold +} + +// /Quorum diff --git a/params/network_params_test.go b/params/network_params_test.go new file mode 100644 index 0000000000..65c6f299e2 --- /dev/null +++ b/params/network_params_test.go @@ -0,0 +1,17 @@ +package params + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestQuorumImmutabilityThresholdParams(t *testing.T) { + immutabilityThreshold := GetImmutabilityThreshold() + assert.Equal(t, 90000, immutabilityThreshold) + + // call Set to set the values + SetQuorumImmutabilityThreshold(20000) + immutabilityThreshold = GetImmutabilityThreshold() + assert.Equal(t, 20000, immutabilityThreshold) +} diff --git a/params/protocol_params.go b/params/protocol_params.go index eae935743c..1a9863d317 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -19,9 +19,14 @@ package params import "math/big" const ( - GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations. - MinGasLimit uint64 = 5000 // Minimum the gas limit may ever be. - GenesisGasLimit uint64 = 4712388 // Gas limit of the Genesis block. + // these are original values from upstream Geth, used in ethash consensus + OriginalMinGasLimit uint64 = 5000 // The bound divisor of the gas limit, used in update calculations. + OriginalGasLimitBoundDivisor uint64 = 1024 // Minimum the gas limit may ever be. + + // modified values for Quorum + GasLimitBoundDivisor uint64 = 4096 // The bound divisor of the gas limit, used in update calculations. + MinGasLimit uint64 = 700000000 // Minimum the gas limit may ever be. + GenesisGasLimit uint64 = 800000000 // Gas limit of the Genesis block. MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis. ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. @@ -139,6 +144,10 @@ const ( Bls12381PairingPerPairGas uint64 = 23000 // Per-point pair gas price for BLS12-381 elliptic curve pairing check Bls12381MapG1Gas uint64 = 5500 // Gas price for BLS12-381 mapping field element to G1 operation Bls12381MapG2Gas uint64 = 110000 // Gas price for BLS12-381 mapping field element to G2 operation + + QuorumMaximumExtraDataSize uint64 = 65 // Maximum size extra data may be after Genesis. + // Quorum - payload for a transaction, the size of the buffer to 128kb to match the maximum allowed in chain config + QuorumMaxPayloadBufferSize uint64 = 128 ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations @@ -150,3 +159,11 @@ var ( MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. ) + +func GetMaximumExtraDataSize(isQuorum bool) uint64 { + if isQuorum { + return QuorumMaximumExtraDataSize + } else { + return MaximumExtraDataSize + } +} diff --git a/params/protocol_params_test.go b/params/protocol_params_test.go new file mode 100644 index 0000000000..104a00231e --- /dev/null +++ b/params/protocol_params_test.go @@ -0,0 +1,25 @@ +package params + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +//Quorum - test key constant values modified by Quorum +func TestQuorumParams(t *testing.T) { + type data struct { + actual uint64 + expected uint64 + } + var testData = map[string]data{ + "GasLimitBoundDivisor": {GasLimitBoundDivisor, 4096}, + "MinGasLimit": {MinGasLimit, 700000000}, + "GenesisGasLimit": {GenesisGasLimit, 800000000}, + "QuorumMaximumExtraDataSize": {QuorumMaximumExtraDataSize, 65}, + "QuorumMaxPayloadBufferSize": {QuorumMaxPayloadBufferSize, 128}, + } + for k, v := range testData { + assert.Equal(t, v.expected, v.actual, k+" value mismatch") + } +} diff --git a/params/quorum.go b/params/quorum.go new file mode 100644 index 0000000000..0eddcd52b4 --- /dev/null +++ b/params/quorum.go @@ -0,0 +1,12 @@ +package params + +const ( + PERMISSIONED_CONFIG = "permissioned-nodes.json" + BLACKLIST_CONFIG = "disallowed-nodes.json" + PERMISSION_MODEL_CONFIG = "permission-config.json" + DEFAULT_ORGCACHE_SIZE = 2000 + DEFAULT_ROLECACHE_SIZE = 2500 + DEFAULT_NODECACHE_SIZE = 1000 + DEFAULT_ACCOUNTCACHE_SIZE = 6000 + NODE_NAME_LENGTH = 32 +) diff --git a/params/version.go b/params/version.go index c66775d324..ae9c621010 100644 --- a/params/version.go +++ b/params/version.go @@ -25,6 +25,10 @@ const ( VersionMinor = 9 // Minor version component of the current release VersionPatch = 17 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string + + QuorumVersionMajor = 21 + QuorumVersionMinor = 1 + QuorumVersionPatch = 0 ) // Version holds the textual version string. @@ -41,6 +45,11 @@ var VersionWithMeta = func() string { return v }() +// Version holds the textual version string. +var QuorumVersion = func() string { + return fmt.Sprintf("%d.%d.%d", QuorumVersionMajor, QuorumVersionMinor, QuorumVersionPatch) +}() + // ArchiveVersion holds the textual version string used for Geth archives. // e.g. "1.8.11-dea1ce05" for stable releases, or // "1.8.13-unstable-21c059b6" for unstable releases @@ -63,5 +72,8 @@ func VersionWithCommit(gitCommit, gitDate string) string { if (VersionMeta != "stable") && (gitDate != "") { vsn += "-" + gitDate } + + vsn += "(quorum-v" + QuorumVersion + ")" + return vsn } diff --git a/permission/api.go b/permission/api.go new file mode 100644 index 0000000000..b64f4454ae --- /dev/null +++ b/permission/api.go @@ -0,0 +1,1095 @@ +package permission + +import ( + "errors" + "fmt" + "math/big" + "regexp" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" +) + +var isStringAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9_-]*$`).MatchString + +//default gas limit to use if not passed in sendTxArgs +var defaultGasLimit = uint64(4712384) + +//default gas price to use if not passed in sendTxArgs +var defaultGasPrice = big.NewInt(0) + +// PermAction represents actions in permission contract +type PermAction int + +const ( + AddOrg PermAction = iota + ApproveOrg + AddSubOrg + UpdateOrgStatus + ApproveOrgStatus + AddNode + UpdateNodeStatus + AssignAdminRole + ApproveAdminRole + AddNewRole + RemoveRole + AddAccountToOrg + ChangeAccountRole + UpdateAccountStatus + InitiateNodeRecovery + InitiateAccountRecovery + ApproveNodeRecovery + ApproveAccountRecovery +) + +type AccountUpdateAction int + +const ( + SuspendAccount AccountUpdateAction = iota + 1 + ActivateSuspendedAccount + BlacklistAccount + RecoverBlacklistedAccount + ApproveBlacklistedAccountRecovery +) + +type NodeUpdateAction int + +const ( + SuspendNode NodeUpdateAction = iota + 1 + ActivateSuspendedNode + BlacklistNode + RecoverBlacklistedNode + ApproveBlacklistedNodeRecovery +) + +type OrgUpdateAction int + +const ( + SuspendOrg OrgUpdateAction = iota + 1 + ActivateSuspendedOrg +) + +// QuorumControlsAPI provides an API to access Quorum's enterprise permissions related services +type QuorumControlsAPI struct { + permCtrl *PermissionCtrl +} + +type PendingOpInfo struct { + PendingKey string `json:"pendingKey"` + PendingOp string `json:"pendingOp"` +} + +var actionSuccess = "Action completed successfully" + +// NewQuorumControlsAPI creates a new QuorumControlsAPI to access quorum services +func NewQuorumControlsAPI(p *PermissionCtrl) *QuorumControlsAPI { + return &QuorumControlsAPI{p} +} + +func (q *QuorumControlsAPI) OrgList() []core.OrgInfo { + return core.OrgInfoMap.GetOrgList() +} + +func (q *QuorumControlsAPI) NodeList() []core.NodeInfo { + return core.NodeInfoMap.GetNodeList() +} + +func (q *QuorumControlsAPI) RoleList() []core.RoleInfo { + return core.RoleInfoMap.GetRoleList() +} + +func (q *QuorumControlsAPI) AcctList() []core.AccountInfo { + return core.AcctInfoMap.GetAcctList() +} + +func (q *QuorumControlsAPI) GetOrgDetails(orgId string) (core.OrgDetailInfo, error) { + o, err := core.OrgInfoMap.GetOrg(orgId) + if err != nil { + return core.OrgDetailInfo{}, err + } + + if o == nil { + return core.OrgDetailInfo{}, errors.New("org does not exist") + } + var acctList []core.AccountInfo + var roleList []core.RoleInfo + var nodeList []core.NodeInfo + for _, a := range q.AcctList() { + if a.OrgId == orgId { + acctList = append(acctList, a) + } + } + for _, a := range q.RoleList() { + if a.OrgId == orgId { + roleList = append(roleList, a) + } + } + for _, a := range q.NodeList() { + if a.OrgId == orgId { + nodeList = append(nodeList, a) + } + } + orgRec, err := core.OrgInfoMap.GetOrg(orgId) + if err != nil { + return core.OrgDetailInfo{}, err + } + + if orgRec == nil { + return core.OrgDetailInfo{NodeList: nodeList, RoleList: roleList, AcctList: acctList}, nil + } + return core.OrgDetailInfo{NodeList: nodeList, RoleList: roleList, AcctList: acctList, SubOrgList: orgRec.SubOrgList}, nil +} + +func reportExecError(action PermAction, err error) (string, error) { + log.Error("Failed to execute permission action", "action", action, "err", err) + msg := fmt.Sprintf("failed to execute permissions action: %v", err) + return "", errors.New(msg) +} + +func (q *QuorumControlsAPI) AddOrg(orgId string, url string, acct common.Address, txa ethapi.SendTxArgs) (string, error) { + orgService, err := q.permCtrl.NewPermissionOrgService(txa) + + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Url: url, AcctId: acct, Txa: txa} + + if err := q.valAddOrg(args); err != nil { + return "", err + } + tx, err := orgService.AddOrg(args) + if err != nil { + return reportExecError(AddOrg, err) + } + log.Debug("executed permission action", "action", AddOrg, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) AddSubOrg(porgId, orgId string, url string, txa ethapi.SendTxArgs) (string, error) { + orgService, err := q.permCtrl.NewPermissionOrgService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{POrgId: porgId, OrgId: orgId, Url: url, Txa: txa} + + if err := q.valAddSubOrg(args); err != nil { + return "", err + } + tx, err := orgService.AddSubOrg(args) + if err != nil { + return reportExecError(AddSubOrg, err) + } + log.Debug("executed permission action", "action", AddSubOrg, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) ApproveOrg(orgId string, url string, acct common.Address, txa ethapi.SendTxArgs) (string, error) { + orgService, err := q.permCtrl.NewPermissionOrgService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Url: url, AcctId: acct, Txa: txa} + if err := q.valApproveOrg(args); err != nil { + return "", err + } + tx, err := orgService.ApproveOrg(args) + if err != nil { + return reportExecError(ApproveOrg, err) + } + log.Debug("executed permission action", "action", ApproveOrg, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) UpdateOrgStatus(orgId string, status uint8, txa ethapi.SendTxArgs) (string, error) { + orgService, err := q.permCtrl.NewPermissionOrgService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Action: status, Txa: txa} + if err := q.valUpdateOrgStatus(args); err != nil { + return "", err + } + // and in suspended state for suspension revoke + tx, err := orgService.UpdateOrgStatus(args) + if err != nil { + return reportExecError(UpdateOrgStatus, err) + } + log.Debug("executed permission action", "action", UpdateOrgStatus, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) AddNode(orgId string, url string, txa ethapi.SendTxArgs) (string, error) { + nodeService, err := q.permCtrl.NewPermissionNodeService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Url: url, Txa: txa} + if err := q.valAddNode(args); err != nil { + return "", err + } + // check if node is already there + tx, err := nodeService.AddNode(args) + if err != nil { + return reportExecError(AddNode, err) + } + log.Debug("executed permission action", "action", AddNode, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) UpdateNodeStatus(orgId string, url string, action uint8, txa ethapi.SendTxArgs) (string, error) { + nodeService, err := q.permCtrl.NewPermissionNodeService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Url: url, Action: action, Txa: txa} + if err := q.valUpdateNodeStatus(args, UpdateNodeStatus); err != nil { + return "", err + } + // check node status for operation + tx, err := nodeService.UpdateNodeStatus(args) + if err != nil { + return reportExecError(UpdateNodeStatus, err) + } + log.Debug("executed permission action", "action", UpdateNodeStatus, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) ApproveOrgStatus(orgId string, status uint8, txa ethapi.SendTxArgs) (string, error) { + orgService, err := q.permCtrl.NewPermissionOrgService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Action: status, Txa: txa} + if err := q.valApproveOrgStatus(args); err != nil { + return "", err + } + // validate that status change is pending approval + tx, err := orgService.ApproveOrgStatus(args) + if err != nil { + return reportExecError(ApproveOrgStatus, err) + } + log.Debug("executed permission action", "action", ApproveOrgStatus, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) AssignAdminRole(orgId string, acct common.Address, roleId string, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, AcctId: acct, RoleId: roleId, Txa: txa} + if err := q.valAssignAdminRole(args); err != nil { + return "", err + } + // check if account is already in use in another org + tx, err := accountService.AssignAdminRole(args) + if err != nil { + return reportExecError(AssignAdminRole, err) + } + log.Debug("executed permission action", "action", AssignAdminRole, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) ApproveAdminRole(orgId string, acct common.Address, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, AcctId: acct, Txa: txa} + if err := q.valApproveAdminRole(args); err != nil { + return "", err + } + // check if anything is pending approval + tx, err := accountService.ApproveAdminRole(args) + if err != nil { + return reportExecError(ApproveAdminRole, err) + } + log.Debug("executed permission action", "action", ApproveAdminRole, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) AddNewRole(orgId string, roleId string, access uint8, isVoter bool, isAdmin bool, txa ethapi.SendTxArgs) (string, error) { + roleService, err := q.permCtrl.NewPermissionRoleService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, RoleId: roleId, AccessType: access, IsVoter: isVoter, IsAdmin: isAdmin, Txa: txa} + if err := q.valAddNewRole(args); err != nil { + return "", err + } + // check if role is already there in the org + tx, err := roleService.AddNewRole(args) + if err != nil { + return reportExecError(AddNewRole, err) + } + log.Debug("executed permission action", "action", AddNewRole, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) RemoveRole(orgId string, roleId string, txa ethapi.SendTxArgs) (string, error) { + roleService, err := q.permCtrl.NewPermissionRoleService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, RoleId: roleId, Txa: txa} + + if err := q.valRemoveRole(args); err != nil { + return "", err + } + tx, err := roleService.RemoveRole(args) + if err != nil { + return reportExecError(RemoveRole, err) + } + log.Debug("executed permission action", "action", RemoveRole, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) AddAccountToOrg(acct common.Address, orgId string, roleId string, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, RoleId: roleId, AcctId: acct, Txa: txa} + + if err := q.valAssignRole(args); err != nil { + return "", err + } + tx, err := accountService.AssignAccountRole(args) + if err != nil { + return reportExecError(AddAccountToOrg, err) + } + log.Debug("executed permission action", "action", AddAccountToOrg, "tx", tx) + return actionSuccess, nil +} +func (q *QuorumControlsAPI) ChangeAccountRole(acct common.Address, orgId string, roleId string, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, RoleId: roleId, AcctId: acct, Txa: txa} + + if err := q.valAssignRole(args); err != nil { + return "", err + } + tx, err := accountService.AssignAccountRole(args) + if err != nil { + return reportExecError(ChangeAccountRole, err) + } + log.Debug("executed permission action", "action", ChangeAccountRole, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) UpdateAccountStatus(orgId string, acct common.Address, status uint8, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, AcctId: acct, Action: status, Txa: txa} + + if err := q.valUpdateAccountStatus(args, UpdateAccountStatus); err != nil { + return "", err + } + tx, err := accountService.UpdateAccountStatus(args) + if err != nil { + return reportExecError(UpdateAccountStatus, err) + } + log.Debug("executed permission action", "action", UpdateAccountStatus, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) RecoverBlackListedNode(orgId string, enodeId string, txa ethapi.SendTxArgs) (string, error) { + nodeService, err := q.permCtrl.NewPermissionNodeService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Url: enodeId, Txa: txa} + + if err := q.valRecoverNode(args, InitiateNodeRecovery); err != nil { + return "", err + } + tx, err := nodeService.StartBlacklistedNodeRecovery(args) + if err != nil { + return reportExecError(InitiateNodeRecovery, err) + } + log.Debug("executed permission action", "action", InitiateNodeRecovery, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) ApproveBlackListedNodeRecovery(orgId string, enodeId string, txa ethapi.SendTxArgs) (string, error) { + nodeService, err := q.permCtrl.NewPermissionNodeService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, Url: enodeId, Txa: txa} + + if err := q.valRecoverNode(args, ApproveNodeRecovery); err != nil { + return "", err + } + tx, err := nodeService.ApproveBlacklistedNodeRecovery(args) + if err != nil { + return reportExecError(ApproveNodeRecovery, err) + } + log.Debug("executed permission action", "action", ApproveNodeRecovery, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) RecoverBlackListedAccount(orgId string, acctId common.Address, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, AcctId: acctId, Txa: txa} + + if err := q.valRecoverAccount(args, InitiateAccountRecovery); err != nil { + return "", err + } + tx, err := accountService.StartBlacklistedAccountRecovery(args) + if err != nil { + return reportExecError(InitiateAccountRecovery, err) + } + log.Debug("executed permission action", "action", InitiateAccountRecovery, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) ApproveBlackListedAccountRecovery(orgId string, acctId common.Address, txa ethapi.SendTxArgs) (string, error) { + accountService, err := q.permCtrl.NewPermissionAccountService(txa) + if err != nil { + return "", err + } + args := ptype.TxArgs{OrgId: orgId, AcctId: acctId, Txa: txa} + + if err := q.valRecoverAccount(args, ApproveAccountRecovery); err != nil { + return "", err + } + tx, err := accountService.ApproveBlacklistedAccountRecovery(args) + if err != nil { + return reportExecError(ApproveAccountRecovery, err) + } + log.Debug("executed permission action", "action", ApproveAccountRecovery, "tx", tx) + return actionSuccess, nil +} + +func (q *QuorumControlsAPI) TransactionAllowed(txa ethapi.SendTxArgs) bool { + var value, gasPrice, gasLimit *big.Int + var payload []byte + var to, from common.Address + if txa.Value != nil { + value = txa.Value.ToInt() + } else { + value = big.NewInt(0) + } + from = txa.From + if txa.To == nil { + to = common.Address{} + } else { + to = *txa.To + } + + if txa.GasPrice != nil { + gasPrice = txa.GasPrice.ToInt() + } else { + gasPrice = big.NewInt(0) + } + + if txa.Gas != nil { + gasLimit = big.NewInt(int64(*txa.Gas)) + } else { + gasLimit = big.NewInt(0) + } + + if txa.Data != nil { + payload = *txa.Data + } + + transactionType := core.ValueTransferTxn + + if txa.To == nil { + transactionType = core.ContractDeployTxn + } else if txa.Data != nil { + transactionType = core.ContractCallTxn + } + + if err := core.IsTransactionAllowed(from, to, value, gasPrice, gasLimit, payload, transactionType); err != nil { + return false + } else { + return true + } +} + +func (q *QuorumControlsAPI) ConnectionAllowed(enodeId, ip string, port, raftPort uint16) bool { + controlService, err := q.permCtrl.NewPermissionControlService() + if err != nil { + return false + } + + if allowed, err := controlService.ConnectionAllowed(enodeId, ip, port, raftPort); err != nil { + return false + } else { + return allowed + } +} + +// check if the account is network admin +func (q *QuorumControlsAPI) isNetworkAdmin(account common.Address) bool { + ac, _ := core.AcctInfoMap.GetAccount(account) + return ac != nil && ac.RoleId == q.permCtrl.permConfig.NwAdminRole +} + +func (q *QuorumControlsAPI) isOrgAdmin(account common.Address, orgId string) error { + org, err := core.OrgInfoMap.GetOrg(orgId) + if err != nil { + return err + } + if org == nil { + return ptype.ErrOrgDoesNotExists + } + ac, _ := core.AcctInfoMap.GetAccount(account) + if ac == nil { + return ptype.ErrNotOrgAdmin + } + // check if the account is network admin + if !(ac.IsOrgAdmin && (ac.OrgId == orgId || ac.OrgId == org.UltimateParent)) { + return ptype.ErrNotOrgAdmin + } + return nil +} + +func (q *QuorumControlsAPI) validateOrg(orgId, pOrgId string) error { + // validate Parent org id + if pOrgId != "" { + if _, err := core.OrgInfoMap.GetOrg(pOrgId); err != nil { + return ptype.ErrInvalidParentOrg + } + locOrgId := pOrgId + "." + orgId + if lorgRec, _ := core.OrgInfoMap.GetOrg(locOrgId); lorgRec != nil { + return ptype.ErrOrgExists + } + } else if orgRec, _ := core.OrgInfoMap.GetOrg(orgId); orgRec != nil { + return ptype.ErrOrgExists + } + return nil +} + +func (q *QuorumControlsAPI) validatePendingOp(authOrg, orgId, url string, account common.Address, pendingOp int64) bool { + auditService, err := q.permCtrl.NewPermissionAuditService() + if err != nil { + return false + } + return auditService.ValidatePendingOp(authOrg, orgId, url, account, pendingOp) +} + +func (q *QuorumControlsAPI) checkPendingOp(orgId string) bool { + auditService, err := q.permCtrl.NewPermissionAuditService() + if err != nil { + return false + } + return auditService.CheckPendingOp(orgId) +} + +func (q *QuorumControlsAPI) checkOrgStatus(orgId string, op uint8) error { + org, _ := core.OrgInfoMap.GetOrg(orgId) + + if org == nil { + return ptype.ErrOrgDoesNotExists + } + // check if its a master org. operation is allowed only if its a master org + if org.Level.Cmp(big.NewInt(1)) != 0 { + return ptype.ErrNotMasterOrg + } + + if !((op == 1 && org.Status == core.OrgApproved) || (op == 2 && org.Status == core.OrgSuspended)) { + return ptype.ErrOpNotAllowed + } + return nil +} + +func (q *QuorumControlsAPI) valNodeStatusChange(orgId, url string, op NodeUpdateAction, permAction PermAction) error { + // validates if the enode is linked the passed organization + // validate node id and if the status change can happen + if len(url) == 0 { + return ptype.ErrInvalidNode + } + if err := q.valNodeDetails(url); err != nil && err.Error() != ptype.ErrNodePresent.Error() { + return err + } + + node, err := core.NodeInfoMap.GetNodeByUrl(url) + if err != nil { + return err + } + + if node.OrgId != orgId { + return ptype.ErrNodeOrgMismatch + } + + if node.Status == core.NodeBlackListed && op != RecoverBlacklistedNode { + return ptype.ErrBlacklistedNode + } + + // validate the op and node status and check if the op can be performed + if (permAction == UpdateNodeStatus && (op != SuspendNode && op != ActivateSuspendedNode && op != BlacklistNode)) || + (permAction == InitiateNodeRecovery && op != RecoverBlacklistedNode) || + (permAction == ApproveNodeRecovery && op != ApproveBlacklistedNodeRecovery) { + return ptype.ErrOpNotAllowed + } + + if (op == SuspendNode && node.Status != core.NodeApproved) || + (op == ActivateSuspendedNode && node.Status != core.NodeDeactivated) || + (op == BlacklistNode && node.Status == core.NodeRecoveryInitiated) || + (op == RecoverBlacklistedNode && node.Status != core.NodeBlackListed) || + (op == ApproveBlacklistedNodeRecovery && node.Status != core.NodeRecoveryInitiated) { + return ptype.ErrOpNotAllowed + } + + return nil +} + +func (q *QuorumControlsAPI) validateRole(orgId, roleId string) bool { + var r *core.RoleInfo + r, err := core.RoleInfoMap.GetRole(orgId, roleId) + if err != nil { + return false + } + + orgRec, err := core.OrgInfoMap.GetOrg(orgId) + if err != nil { + return false + } + r, err = core.RoleInfoMap.GetRole(orgRec.UltimateParent, roleId) + if err != nil { + return false + } + + return r != nil && r.Active +} + +func (q *QuorumControlsAPI) valAccountStatusChange(orgId string, account common.Address, permAction PermAction, op AccountUpdateAction) error { + // validates if the enode is linked the passed organization + ac, err := core.AcctInfoMap.GetAccount(account) + if err != nil { + return err + } + + if ac.IsOrgAdmin && (ac.RoleId == q.permCtrl.permConfig.NwAdminRole || ac.RoleId == q.permCtrl.permConfig.OrgAdminRole) && (op == 1 || op == 3) { + return ptype.ErrOpNotAllowed + } + + if ac.OrgId != orgId { + return ptype.ErrOrgNotOwner + } + if (permAction == UpdateAccountStatus && (op != SuspendAccount && op != ActivateSuspendedAccount && op != BlacklistAccount)) || + (permAction == InitiateAccountRecovery && op != RecoverBlacklistedAccount) || + (permAction == ApproveAccountRecovery && op != ApproveBlacklistedAccountRecovery) { + return ptype.ErrOpNotAllowed + } + + if ac.Status == core.AcctBlacklisted && op != RecoverBlacklistedAccount { + return ptype.ErrBlacklistedAccount + } + + if (op == SuspendAccount && ac.Status != core.AcctActive) || + (op == ActivateSuspendedAccount && ac.Status != core.AcctSuspended) || + (op == BlacklistAccount && ac.Status == core.AcctRecoveryInitiated) || + (op == RecoverBlacklistedAccount && ac.Status != core.AcctBlacklisted) || + (op == ApproveBlacklistedAccountRecovery && ac.Status != core.AcctRecoveryInitiated) { + return ptype.ErrOpNotAllowed + } + return nil +} + +func (q *QuorumControlsAPI) checkOrgAdminExists(orgId, roleId string, account common.Address) error { + if ac, _ := core.AcctInfoMap.GetAccount(account); ac != nil { + if ac.OrgId != orgId { + return ptype.ErrAccountInUse + } + if roleId != "" && roleId == q.permCtrl.permConfig.OrgAdminRole && ac.IsOrgAdmin { + return ptype.ErrAccountOrgAdmin + } + } + return nil +} + +func (q *QuorumControlsAPI) valSubOrgBreadthDepth(porgId string) error { + org, err := core.OrgInfoMap.GetOrg(porgId) + if err != nil { + return ptype.ErrOpNotAllowed + } + + if q.permCtrl.permConfig.SubOrgDepth.Cmp(org.Level) == 0 { + return ptype.ErrMaxDepth + } + + if q.permCtrl.permConfig.SubOrgBreadth.Cmp(big.NewInt(int64(len(org.SubOrgList)))) == 0 { + return ptype.ErrMaxBreadth + } + + return nil +} + +func (q *QuorumControlsAPI) checkNodeExists(url, enodeId string) bool { + node, _ := core.NodeInfoMap.GetNodeByUrl(url) + if node != nil { + return true + } + // check if the same nodeid is in use with different port numbers + nodeList := core.NodeInfoMap.GetNodeList() + for _, n := range nodeList { + if enodeDet, er := enode.ParseV4(n.Url); er == nil { + if enodeDet.EnodeID() == enodeId { + return true + } + } + } + return false +} + +func (q *QuorumControlsAPI) valNodeDetails(url string) error { + // validate node id and + if len(url) != 0 { + enodeDet, err := enode.ParseV4(url) + if err != nil { + return ptype.ErrInvalidNode + } + if q.permCtrl.isRaft && !q.permCtrl.useDns && enodeDet.Host() != "" { + return ptype.ErrHostNameNotSupported + } + // check if node already there + if q.checkNodeExists(url, enodeDet.EnodeID()) { + return ptype.ErrNodePresent + } + } + return nil +} + +// all validations for add org operation +func (q *QuorumControlsAPI) valAddOrg(args ptype.TxArgs) error { + // check if the org id contains "." + if args.OrgId == "" || args.Url == "" || args.AcctId == (common.Address{0}) { + return ptype.ErrInvalidInput + } + if !isStringAlphaNumeric(args.OrgId) { + return ptype.ErrInvalidOrgName + } + + // check if caller is network admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + + // check if any previous op is pending approval for network admin + if q.checkPendingOp(q.permCtrl.permConfig.NwAdminOrg) { + return ptype.ErrPendingApprovals + } + // check if org already exists + if er := q.validateOrg(args.OrgId, ""); er != nil { + return er + } + + // validate node id and + if er := q.valNodeDetails(args.Url); er != nil { + return er + } + + // check if account is already part of another org + if er := q.checkOrgAdminExists(args.OrgId, "", args.AcctId); er != nil { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valApproveOrg(args ptype.TxArgs) error { + // check caller is network admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + // check if anything pending approval + if !q.validatePendingOp(q.permCtrl.permConfig.NwAdminOrg, args.OrgId, args.Url, args.AcctId, 1) { + return ptype.ErrNothingToApprove + } + return nil +} + +func (q *QuorumControlsAPI) valAddSubOrg(args ptype.TxArgs) error { + // check if the org id contains "." + if args.OrgId == "" { + return ptype.ErrInvalidInput + } + if !isStringAlphaNumeric(args.OrgId) { + return ptype.ErrInvalidOrgName + } + + // check if caller is network admin + if er := q.isOrgAdmin(args.Txa.From, args.POrgId); er != nil { + return er + } + + // check if org already exists + if er := q.validateOrg(args.OrgId, args.POrgId); er != nil { + return er + } + + if er := q.valSubOrgBreadthDepth(args.POrgId); er != nil { + return er + } + + if er := q.valNodeDetails(args.Url); er != nil { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valUpdateOrgStatus(args ptype.TxArgs) error { + // check if called is network admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + if OrgUpdateAction(args.Action) != SuspendOrg && + OrgUpdateAction(args.Action) != ActivateSuspendedOrg { + return ptype.ErrOpNotAllowed + } + + //check if passed org id is network admin org. update should not be allowed + if args.OrgId == q.permCtrl.permConfig.NwAdminOrg { + return ptype.ErrOpNotAllowed + } + // check if status update can be performed. Org should be approved for suspension + if er := q.checkOrgStatus(args.OrgId, args.Action); er != nil { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valApproveOrgStatus(args ptype.TxArgs) error { + // check if called is network admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + // check if anything is pending approval + var pendingOp int64 + if args.Action == 1 { + pendingOp = 2 + } else if args.Action == 2 { + pendingOp = 3 + } else { + return ptype.ErrOpNotAllowed + } + if !q.validatePendingOp(q.permCtrl.permConfig.NwAdminOrg, args.OrgId, "", common.Address{}, pendingOp) { + return ptype.ErrNothingToApprove + } + return nil +} + +func (q *QuorumControlsAPI) valAddNode(args ptype.TxArgs) error { + if args.Url == "" { + return ptype.ErrInvalidInput + } + // check if caller is network admin + if er := q.isOrgAdmin(args.Txa.From, args.OrgId); er != nil { + return er + } + + if er := q.valNodeDetails(args.Url); er != nil { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valUpdateNodeStatus(args ptype.TxArgs, permAction PermAction) error { + // check if org admin + // check if caller is network admin + if er := q.isOrgAdmin(args.Txa.From, args.OrgId); er != nil { + return er + } + + // validation status change is with in allowed set + if er := q.valNodeStatusChange(args.OrgId, args.Url, NodeUpdateAction(args.Action), permAction); er != nil { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valAssignAdminRole(args ptype.TxArgs) error { + if args.AcctId == (common.Address{0}) { + return ptype.ErrInvalidInput + } + // check if caller is network admin + if args.RoleId != q.permCtrl.permConfig.OrgAdminRole && args.RoleId != q.permCtrl.permConfig.NwAdminRole { + return ptype.ErrOpNotAllowed + } + + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + + if err := q.validateOrg(args.OrgId, ""); err == nil { + return ptype.ErrOrgDoesNotExists + } + + // check if account is already part of another org + if er := q.checkOrgAdminExists(args.OrgId, args.RoleId, args.AcctId); er != nil && er.Error() != ptype.ErrOrgAdminExists.Error() { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valApproveAdminRole(args ptype.TxArgs) error { + // check if caller is network admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + // check if the org exists + + // check if account is valid + ac, _ := core.AcctInfoMap.GetAccount(args.AcctId) + if ac == nil { + return ptype.ErrInvalidAccount + } + // validate pending op + if !q.validatePendingOp(q.permCtrl.permConfig.NwAdminOrg, ac.OrgId, "", args.AcctId, 4) { + return ptype.ErrNothingToApprove + } + return nil +} + +func (q *QuorumControlsAPI) valAddNewRole(args ptype.TxArgs) error { + if args.RoleId == "" { + return ptype.ErrInvalidInput + } + // check if caller is network admin + if er := q.isOrgAdmin(args.Txa.From, args.OrgId); er != nil { + return er + } + // validate if role is already present + if roleRec, _ := core.RoleInfoMap.GetRole(args.OrgId, args.RoleId); roleRec != nil { + return ptype.ErrRoleExists + } + return nil +} + +func (q *QuorumControlsAPI) valRemoveRole(args ptype.TxArgs) error { + // check if caller is network admin + if er := q.isOrgAdmin(args.Txa.From, args.OrgId); er != nil { + return er + } + + // admin roles cannot be removed + if args.RoleId == q.permCtrl.permConfig.OrgAdminRole || args.RoleId == q.permCtrl.permConfig.NwAdminRole { + return ptype.ErrAdminRoles + } + + // check if role is alraedy inactive + r, _ := core.RoleInfoMap.GetRole(args.OrgId, args.RoleId) + if r == nil { + return ptype.ErrInvalidRole + } else if !r.Active { + return ptype.ErrInactiveRole + } + + // check if the role has active accounts. if yes operations should not be allowed + if len(core.AcctInfoMap.GetAcctListRole(args.OrgId, args.RoleId)) != 0 { + return ptype.ErrRoleActive + } + return nil +} + +func (q *QuorumControlsAPI) valAssignRole(args ptype.TxArgs) error { + if args.AcctId == (common.Address{0}) { + return ptype.ErrInvalidInput + } + if args.RoleId == q.permCtrl.permConfig.OrgAdminRole || args.RoleId == q.permCtrl.permConfig.NwAdminRole { + return ptype.ErrInvalidRole + } + // check if caller is network admin + if er := q.isOrgAdmin(args.Txa.From, args.OrgId); er != nil { + return er + } + + // check if the role is valid + if !q.validateRole(args.OrgId, args.RoleId) { + return ptype.ErrInvalidRole + } + + // check if the account is part of another org + if ac, _ := core.AcctInfoMap.GetAccount(args.AcctId); ac != nil { + if ac != nil && ac.OrgId != args.OrgId { + return ptype.ErrAccountInUse + } + } + return nil +} + +func (q *QuorumControlsAPI) valUpdateAccountStatus(args ptype.TxArgs, permAction PermAction) error { + // check if the caller is org admin + if er := q.isOrgAdmin(args.Txa.From, args.OrgId); er != nil { + return er + } + // validation status change is with in allowed set + if er := q.valAccountStatusChange(args.OrgId, args.AcctId, permAction, AccountUpdateAction(args.Action)); er != nil { + return er + } + return nil +} + +func (q *QuorumControlsAPI) valRecoverNode(args ptype.TxArgs, action PermAction) error { + // check if the caller is org admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + // validate inputs - org id is valid, node is valid and in blacklisted state + if err := q.validateOrg(args.OrgId, ""); err.Error() != ptype.ErrOrgExists.Error() { + return ptype.ErrInvalidOrgName + } + + if action == InitiateNodeRecovery { + if err := q.valNodeStatusChange(args.OrgId, args.Url, 4, InitiateAccountRecovery); err != nil { + return err + } + // check no pending approval items + if q.checkPendingOp(q.permCtrl.permConfig.NwAdminOrg) { + return ptype.ErrPendingApprovals + } + } else { + // validate inputs - org id is valid, Node is valid pending recovery state + if err := q.valNodeStatusChange(args.OrgId, args.Url, 5, ApproveNodeRecovery); err != nil { + return err + } + if !q.validatePendingOp(q.permCtrl.permConfig.NwAdminOrg, args.OrgId, args.Url, common.Address{}, 5) { + return ptype.ErrNothingToApprove + } + } + + // if it is approval ensure that + + return nil +} + +func (q *QuorumControlsAPI) valRecoverAccount(args ptype.TxArgs, action PermAction) error { + // check if the caller is org admin + if !q.isNetworkAdmin(args.Txa.From) { + return ptype.ErrNotNetworkAdmin + } + + var opAction AccountUpdateAction + if action == InitiateAccountRecovery { + opAction = RecoverBlacklistedAccount + } else { + opAction = ApproveBlacklistedAccountRecovery + } + + if err := q.valAccountStatusChange(args.OrgId, args.AcctId, action, opAction); err != nil { + return err + } + + if action == InitiateAccountRecovery && q.checkPendingOp(q.permCtrl.permConfig.NwAdminOrg) { + return ptype.ErrPendingApprovals + } + + if action == ApproveAccountRecovery && !q.validatePendingOp(q.permCtrl.permConfig.NwAdminOrg, args.OrgId, "", args.AcctId, 6) { + return ptype.ErrNothingToApprove + } + return nil +} diff --git a/permission/backend.go b/permission/backend.go new file mode 100644 index 0000000000..b9f3fde3e1 --- /dev/null +++ b/permission/backend.go @@ -0,0 +1,284 @@ +package permission + +import ( + "crypto/ecdsa" + "errors" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" + v1 "github.com/ethereum/go-ethereum/permission/v1" + v2 "github.com/ethereum/go-ethereum/permission/v2" + "github.com/ethereum/go-ethereum/rpc" +) + +type PermissionCtrl struct { + node *node.Node + ethClnt bind.ContractBackend + eth *eth.Ethereum + key *ecdsa.PrivateKey + dataDir string + permConfig *ptype.PermissionConfig + contract ptype.InitService + backend ptype.Backend + useDns bool + isRaft bool + startWaitGroup *sync.WaitGroup // waitgroup to make sure all dependencies are ready before we start the service + errorChan chan error // channel to capture error when starting aysnc + networkInitialized bool + controlService ptype.ControlService +} + +var permissionService *PermissionCtrl + +func setPermissionService(ps *PermissionCtrl) { + if permissionService == nil { + permissionService = ps + } +} + +// Create a service instance for permissioning +// +// Permission Service depends on the following: +// 1. EthService to be ready +// 2. Downloader to sync up blocks +// 3. InProc RPC server to be ready +func NewQuorumPermissionCtrl(stack *node.Node, pconfig *ptype.PermissionConfig, useDns bool) (*PermissionCtrl, error) { + wg := &sync.WaitGroup{} + wg.Add(1) + + p := &PermissionCtrl{ + node: stack, + key: stack.GetNodeKey(), + dataDir: stack.DataDir(), + permConfig: pconfig, + startWaitGroup: wg, + errorChan: make(chan error), + useDns: useDns, + isRaft: false, + } + + err := p.populateBackEnd() + if err != nil { + return nil, err + } + stopChan, stopSubscription := ptype.SubscribeStopEvent() + inProcRPCServerSub := stack.EventMux().Subscribe(rpc.InProcServerReadyEvent{}) + log.Debug("permission service: waiting for InProcRPC Server") + + go func(_wg *sync.WaitGroup) { + defer func(start time.Time) { + log.Debug("permission service: InProcRPC server is ready", "took", time.Since(start)) + stopSubscription.Unsubscribe() + inProcRPCServerSub.Unsubscribe() + _wg.Done() + }(time.Now()) + select { + case <-inProcRPCServerSub.Chan(): + case <-stopChan: + } + }(wg) // wait for inproc RPC to be ready + return p, nil +} + +func (p *PermissionCtrl) Start(srvr *p2p.Server) error { + log.Info("permission service: starting") + go func() { + log.Info("permission service: starting async") + p.asyncStart() + }() + return nil +} + +func (p *PermissionCtrl) Protocols() []p2p.Protocol { + return []p2p.Protocol{} +} + +func (p *PermissionCtrl) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "quorumPermission", + Version: "1.0", + Service: NewQuorumControlsAPI(p), + Public: true, + }, + } +} + +func (p *PermissionCtrl) Stop() error { + log.Info("permission service: stopping") + ptype.StopFeed.Send(ptype.StopEvent{}) + log.Info("permission service: stopped") + return nil +} + +func (p *PermissionCtrl) IsV2Permission() bool { + return p.permConfig.PermissionsModel == ptype.PERMISSION_V2 +} + +func NewPermissionContractService(ethClnt bind.ContractBackend, permissionV2 bool, key *ecdsa.PrivateKey, + permConfig *ptype.PermissionConfig, isRaft, useDns bool) ptype.InitService { + + contractBackEnd := ptype.ContractBackend{ + EthClnt: ethClnt, + Key: key, + PermConfig: permConfig, + IsRaft: isRaft, + UseDns: useDns, + } + + if permissionV2 { + return &v2.Init{ + Backend: contractBackEnd, + } + } + return &v1.Init{ + Backend: contractBackEnd, + } +} + +func (p *PermissionCtrl) NewPermissionRoleService(txa ethapi.SendTxArgs) (ptype.RoleService, error) { + transactOpts, err := p.getTxParams(txa) + if err != nil { + return nil, err + } + return p.backend.GetRoleService(transactOpts, p.getContractBackend()) +} + +func (p *PermissionCtrl) NewPermissionOrgService(txa ethapi.SendTxArgs) (ptype.OrgService, error) { + transactOpts, err := p.getTxParams(txa) + if err != nil { + return nil, err + } + return p.backend.GetOrgService(transactOpts, p.getContractBackend()) +} + +func (p *PermissionCtrl) NewPermissionNodeService(txa ethapi.SendTxArgs) (ptype.NodeService, error) { + transactOpts, err := p.getTxParams(txa) + if err != nil { + return nil, err + } + return p.backend.GetNodeService(transactOpts, p.getContractBackend()) +} + +func (p *PermissionCtrl) NewPermissionAccountService(txa ethapi.SendTxArgs) (ptype.AccountService, error) { + transactOpts, err := p.getTxParams(txa) + if err != nil { + return nil, err + } + return p.backend.GetAccountService(transactOpts, p.getContractBackend()) +} + +func (p *PermissionCtrl) NewPermissionAuditService() (ptype.AuditService, error) { + return p.backend.GetAuditService(p.getContractBackend()) +} + +func (p *PermissionCtrl) NewPermissionControlService() (ptype.ControlService, error) { + return p.backend.GetControlService(p.getContractBackend()) +} + +func (p *PermissionCtrl) getContractBackend() ptype.ContractBackend { + return ptype.ContractBackend{EthClnt: p.ethClnt, Key: p.key, PermConfig: p.permConfig, IsRaft: p.isRaft, UseDns: p.isRaft} +} + +func (p *PermissionCtrl) ConnectionAllowed(_enodeId, _ip string, _port, _raftPort uint16) (bool, error) { + cs, err := p.backend.GetControlService(p.getContractBackend()) + if err != nil { + return false, err + } + return cs.ConnectionAllowed(_enodeId, _ip, _port, _raftPort) +} + +func (p *PermissionCtrl) IsTransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte, transactionType core.TransactionType) error { + // If permissions model is not in use return nil + if core.PermissionModel == core.Default { + return nil + } + + cs, err := p.backend.GetControlService(p.getContractBackend()) + if err != nil { + return err + } + + return cs.TransactionAllowed(_sender, _target, _value, _gasPrice, _gasLimit, _payload, transactionType) +} + +func (p *PermissionCtrl) populateBackEnd() error { + backend := ptype.NewInterfaceBackend(p.node, false, p.dataDir) + + switch p.permConfig.PermissionsModel { + case ptype.PERMISSION_V2: + p.backend = &v2.Backend{ + Ib: *backend, + } + log.Info("permission service: using v2 permissions model") + return nil + + case ptype.PERMISSION_V1: + p.backend = &v1.Backend{ + Ib: *backend, + } + log.Info("permission service: using v1 permissions model") + return nil + + default: + return errors.New("permission: invalid permissions model passed") + } + +} + +func (p *PermissionCtrl) updateBackEnd() { + p.contract = NewPermissionContractService(p.ethClnt, p.IsV2Permission(), p.key, p.permConfig, p.isRaft, p.useDns) + switch p.IsV2Permission() { + case true: + p.backend.(*v2.Backend).Contr = p.contract.(*v2.Init) + p.backend.(*v2.Backend).Ib.SetIsRaft(p.isRaft) + + default: + p.backend.(*v1.Backend).Contr = p.contract.(*v1.Init) + p.backend.(*v1.Backend).Ib.SetIsRaft(p.isRaft) + } +} + +// validateAccount validates the account and returns the wallet associated with that for signing the transaction +func (p *PermissionCtrl) validateAccount(from common.Address) (accounts.Wallet, error) { + acct := accounts.Account{Address: from} + w, err := p.eth.AccountManager().Find(acct) + if err != nil { + return nil, err + } + return w, nil +} + +// getTxParams extracts the transaction related parameters +func (p *PermissionCtrl) getTxParams(txa ethapi.SendTxArgs) (*bind.TransactOpts, error) { + w, err := p.validateAccount(txa.From) + if err != nil { + return nil, ptype.ErrInvalidAccount + } + fromAcct := accounts.Account{Address: txa.From} + transactOpts := bind.NewWalletTransactor(w, fromAcct) + + transactOpts.GasPrice = defaultGasPrice + if txa.GasPrice != nil { + transactOpts.GasPrice = txa.GasPrice.ToInt() + } + + transactOpts.GasLimit = defaultGasLimit + if txa.Gas != nil { + transactOpts.GasLimit = uint64(*txa.Gas) + } + transactOpts.From = fromAcct.Address + + return transactOpts, nil +} diff --git a/permission/connection.go b/permission/connection.go new file mode 100644 index 0000000000..cf60850afa --- /dev/null +++ b/permission/connection.go @@ -0,0 +1,65 @@ +package permission + +import ( + "strings" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/permission/core" +) + +func isNodePermissionedV1(enodeId string, nodename string, currentNode string, direction string) bool { + permissionedList := core.NodeInfoMap.GetNodeList() + + log.Debug("isNodePermissionedV1", "permissionedList", permissionedList) + for _, n := range permissionedList { + if strings.Contains(n.Url, enodeId) && n.Status == core.NodeApproved { + log.Debug("isNodePermissionedV1", "connection", direction, "nodename", nodename[:params.NODE_NAME_LENGTH], "ALLOWED-BY", currentNode[:params.NODE_NAME_LENGTH]) + return true + } + } + log.Debug("isNodePermissionedV1", "connection", direction, "nodename", nodename[:params.NODE_NAME_LENGTH], "DENIED-BY", currentNode[:params.NODE_NAME_LENGTH]) + return false +} + +func isNodePermissionedV2(node *enode.Node, nodename string, currentNode string, direction string) bool { + if permissionService == nil { + log.Debug("isNodePermissionedV2 connection not allowed - permissionService is not set") + return false + } + allowed, err := permissionService.ConnectionAllowed(node.EnodeID(), node.IP().String(), uint16(node.TCP()), uint16(node.RaftPort())) + log.Debug("isNodePermissionedV2 V2", "allowed", allowed, "url", node.String()) + if err != nil { + log.Error("isNodePermissionedV2 connection not allowed", "err", err) + return false + } + if allowed { + log.Debug("isNodePermissionedV2", "connection", direction, "nodename", nodename[:params.NODE_NAME_LENGTH], "ALLOWED-BY", currentNode[:params.NODE_NAME_LENGTH]) + } else { + log.Debug("isNodePermissionedV2", "connection", direction, "nodename", nodename[:params.NODE_NAME_LENGTH], "DENIED-BY", currentNode[:params.NODE_NAME_LENGTH]) + + } + return allowed + +} + +func IsNodePermissioned(node *enode.Node, nodename string, currentNode string, datadir string, direction string) bool { + + //if we have not reached QIP714 block return full access + if !core.PermissionsEnabled() { + return core.IsNodePermissioned(nodename, currentNode, datadir, direction) + } + + switch core.PermissionModel { + case core.Default: + return core.IsNodePermissioned(nodename, currentNode, datadir, direction) + + case core.V1: + return isNodePermissionedV1(node.EnodeID(), nodename, currentNode, direction) + + case core.V2: + return isNodePermissionedV2(node, nodename, currentNode, direction) + } + return false +} diff --git a/permission/core/cache.go b/permission/core/cache.go new file mode 100644 index 0000000000..e0bfa51e67 --- /dev/null +++ b/permission/core/cache.go @@ -0,0 +1,634 @@ +package core + +import ( + "errors" + "fmt" + "math/big" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/enode" + lru "github.com/hashicorp/golang-lru" +) + +type TransactionType uint8 + +const ( + ValueTransferTxn TransactionType = iota + ContractCallTxn + ContractDeployTxn +) + +type AccessType uint8 + +const ( + // common access type list for both V1 and V2 model. + // the first 4 are used by both models + // last 3 are used by V2 in alignment with EEA specs + ReadOnly AccessType = iota + Transact + ContractDeploy + FullAccess + // below access types are only used by V2 model + ContractCall + TransactAndContractCall + TransactAndContractDeploy + ContractCallAndDeploy +) + +type PermissionModelType uint8 + +const ( + V1 PermissionModelType = iota + V2 + Default +) + +type OrgStatus uint8 + +const ( + OrgPendingApproval OrgStatus = iota + 1 + OrgApproved + OrgPendingSuspension + OrgSuspended +) + +type OrgInfo struct { + OrgId string `json:"orgId"` + FullOrgId string `json:"fullOrgId"` + ParentOrgId string `json:"parentOrgId"` + UltimateParent string `json:"ultimateParent"` + Level *big.Int `json:"level"` + SubOrgList []string `json:"subOrgList"` + Status OrgStatus `json:"status"` +} + +type NodeStatus uint8 + +const ( + NodePendingApproval NodeStatus = iota + 1 + NodeApproved + NodeDeactivated + NodeBlackListed + NodeRecoveryInitiated +) + +type AcctStatus uint8 + +const ( + AcctPendingApproval AcctStatus = iota + 1 + AcctActive + AcctInactive + AcctSuspended + AcctBlacklisted + AdminRevoked + AcctRecoveryInitiated + AcctRecoveryCompleted +) + +type NodeInfo struct { + OrgId string `json:"orgId"` + Url string `json:"url"` + Status NodeStatus `json:"status"` +} + +type RoleInfo struct { + OrgId string `json:"orgId"` + RoleId string `json:"roleId"` + IsVoter bool `json:"isVoter"` + IsAdmin bool `json:"isAdmin"` + Access AccessType `json:"access"` + Active bool `json:"active"` +} + +type AccountInfo struct { + OrgId string `json:"orgId"` + RoleId string `json:"roleId"` + AcctId common.Address `json:"acctId"` + IsOrgAdmin bool `json:"isOrgAdmin"` + Status AcctStatus `json:"status"` +} + +type OrgDetailInfo struct { + NodeList []NodeInfo `json:"nodeList"` + RoleList []RoleInfo `json:"roleList"` + AcctList []AccountInfo `json:"acctList"` + SubOrgList []string `json:"subOrgList"` +} + +var syncStarted = false + +var defaultAccess = FullAccess +var qip714BlockReached = false +var networkBootUpCompleted = false +var networkAdminRole string +var orgAdminRole string +var PermissionModel = Default +var PermissionTransactionAllowedFunc func(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte, _transactionType TransactionType) error +var ( + OrgInfoMap *OrgCache + NodeInfoMap *NodeCache + RoleInfoMap *RoleCache + AcctInfoMap *AcctCache +) + +type OrgKey struct { + OrgId string +} + +type OrgCache struct { + c *lru.Cache + mux sync.Mutex + evicted bool + populateCacheFunc func(orgId string) (*OrgInfo, error) +} + +func (o *OrgCache) PopulateCacheFunc(cf func(string) (*OrgInfo, error)) { + o.populateCacheFunc = cf +} + +func NewOrgCache(cacheSize int) *OrgCache { + orgCache := OrgCache{evicted: false} + onEvictedFunc := func(k interface{}, v interface{}) { + orgCache.evicted = true + } + orgCache.c, _ = lru.NewWithEvict(cacheSize, onEvictedFunc) + return &orgCache +} + +type RoleKey struct { + OrgId string + RoleId string +} + +type RoleCache struct { + c *lru.Cache + evicted bool + populateCacheFunc func(*RoleKey) (*RoleInfo, error) +} + +func (r *RoleCache) PopulateCacheFunc(cf func(*RoleKey) (*RoleInfo, error)) { + r.populateCacheFunc = cf +} + +func NewRoleCache(cacheSize int) *RoleCache { + roleCache := RoleCache{evicted: false} + onEvictedFunc := func(k interface{}, v interface{}) { + roleCache.evicted = true + } + roleCache.c, _ = lru.NewWithEvict(cacheSize, onEvictedFunc) + return &roleCache +} + +type NodeKey struct { + OrgId string + Url string +} + +type NodeCache struct { + c *lru.Cache + evicted bool + populateCacheFunc func(string) (*NodeInfo, error) + populateAndValidateFunc func(string, string) bool +} + +func (n *NodeCache) PopulateValidateFunc(cf func(string, string) bool) { + n.populateAndValidateFunc = cf +} + +func (n *NodeCache) PopulateCacheFunc(cf func(string) (*NodeInfo, error)) { + n.populateCacheFunc = cf +} + +func NewNodeCache(cacheSize int) *NodeCache { + nodeCache := NodeCache{evicted: false} + onEvictedFunc := func(k interface{}, v interface{}) { + nodeCache.evicted = true + + } + nodeCache.c, _ = lru.NewWithEvict(cacheSize, onEvictedFunc) + return &nodeCache +} + +type AccountKey struct { + AcctId common.Address +} + +type AcctCache struct { + c *lru.Cache + evicted bool + populateCacheFunc func(account common.Address) (*AccountInfo, error) +} + +func (a *AcctCache) PopulateCacheFunc(cf func(common.Address) (*AccountInfo, error)) { + a.populateCacheFunc = cf +} + +func NewAcctCache(cacheSize int) *AcctCache { + acctCache := AcctCache{evicted: false} + onEvictedFunc := func(k interface{}, v interface{}) { + acctCache.evicted = true + } + + acctCache.c, _ = lru.NewWithEvict(cacheSize, onEvictedFunc) + return &acctCache +} + +func SetSyncStatus() { + syncStarted = true +} + +func GetSyncStatus() bool { + return syncStarted +} + +// sets default access to read only +func setDefaultAccess() { + if PermissionsEnabled() { + defaultAccess = ReadOnly + } +} + +// sets the qip714block reached as true +func SetQIP714BlockReached() { + qip714BlockReached = true + setDefaultAccess() +} + +// sets the network boot completed as true +func SetNetworkBootUpCompleted() { + networkBootUpCompleted = true + setDefaultAccess() +} + +// return bool to indicate if permissions is enabled +func PermissionsEnabled() bool { + if PermissionModel == V2 { + return qip714BlockReached + } else { + return qip714BlockReached && networkBootUpCompleted + } +} + +// sets default access to readonly and initializes the values for +// network admin role and org admin role +func SetDefaults(nwRoleId, oaRoleId string, permissionV2 bool) { + networkAdminRole = nwRoleId + orgAdminRole = oaRoleId + if permissionV2 { + PermissionModel = V2 + } else { + PermissionModel = V1 + } +} + +func GetDefaults() (string, string, AccessType) { + return networkAdminRole, orgAdminRole, defaultAccess +} + +func GetNodeUrl(enodeId string, ip string, port uint16, raftport uint16, isRaft bool) string { + if isRaft { + return fmt.Sprintf("enode://%s@%s:%d?discport=0&raftport=%d", enodeId, strings.Trim(ip, "\x00"), port, raftport) + } + return fmt.Sprintf("enode://%s@%s:%d?discport=0", enodeId, strings.Trim(ip, "\x00"), port) +} + +func (o *OrgCache) UpsertOrg(orgId, parentOrg, ultimateParent string, level *big.Int, status OrgStatus) { + defer o.mux.Unlock() + o.mux.Lock() + var key OrgKey + if parentOrg == "" { + key = OrgKey{OrgId: orgId} + } else { + key = OrgKey{OrgId: parentOrg + "." + orgId} + pkey := OrgKey{OrgId: parentOrg} + if ent, ok := o.c.Get(pkey); ok { + porg := ent.(*OrgInfo) + if !containsKey(porg.SubOrgList, key.OrgId) { + porg.SubOrgList = append(porg.SubOrgList, key.OrgId) + o.c.Add(pkey, porg) + } + } + } + + norg := &OrgInfo{orgId, key.OrgId, parentOrg, ultimateParent, level, nil, status} + o.c.Add(key, norg) +} + +func (o *OrgCache) UpsertOrgWithSubOrgList(orgRec *OrgInfo) { + var key OrgKey + if orgRec.ParentOrgId == "" { + key = OrgKey{OrgId: orgRec.OrgId} + } else { + key = OrgKey{OrgId: orgRec.ParentOrgId + "." + orgRec.OrgId} + } + orgRec.FullOrgId = key.OrgId + o.c.Add(key, orgRec) +} + +func containsKey(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func (o *OrgCache) GetOrg(orgId string) (*OrgInfo, error) { + key := OrgKey{OrgId: orgId} + if ent, ok := o.c.Get(key); ok { + return ent.(*OrgInfo), nil + } + // check if the org cache is evicted. if yes we need + // fetch the record from the contract + if o.evicted { + // call cache population function to populate from contract + orgRec, err := o.populateCacheFunc(orgId) + if err != nil { + return nil, err + } + // insert the received record into cache + o.UpsertOrgWithSubOrgList(orgRec) + //return the record + return orgRec, nil + } + return nil, errors.New("Org does not exist") +} + +func (o *OrgCache) GetOrgList() []OrgInfo { + olist := make([]OrgInfo, len(o.c.Keys())) + for i, k := range o.c.Keys() { + v, _ := o.c.Get(k) + vp := v.(*OrgInfo) + olist[i] = *vp + } + return olist +} + +func (n *NodeCache) UpsertNode(orgId string, url string, status NodeStatus) { + key := NodeKey{OrgId: orgId, Url: url} + n.c.Add(key, &NodeInfo{orgId, url, status}) +} + +func (n *NodeCache) GetNodeByUrl(url string) (*NodeInfo, error) { + for _, k := range n.c.Keys() { + ent := k.(NodeKey) + if ent.Url == url { + v, _ := n.c.Get(ent) + return v.(*NodeInfo), nil + } + } + // check if the node cache is evicted. if yes we need + // fetch the record from the contract + if n.evicted { + + // call cache population function to populate from contract + nodeRec, err := n.populateCacheFunc(url) + if err != nil { + return nil, err + } + + // insert the received record into cache + n.UpsertNode(nodeRec.OrgId, nodeRec.Url, nodeRec.Status) + //return the record + return nodeRec, err + } + return nil, errors.New("Node does not exist") +} + +func (n *NodeCache) GetNodeList() []NodeInfo { + olist := make([]NodeInfo, len(n.c.Keys())) + for i, k := range n.c.Keys() { + v, _ := n.c.Get(k) + vp := v.(*NodeInfo) + olist[i] = *vp + } + return olist +} + +func (a *AcctCache) UpsertAccount(orgId string, role string, acct common.Address, orgAdmin bool, status AcctStatus) { + key := AccountKey{acct} + a.c.Add(key, &AccountInfo{orgId, role, acct, orgAdmin, status}) +} + +func (a *AcctCache) GetAccount(acct common.Address) (*AccountInfo, error) { + if v, ok := a.c.Get(AccountKey{acct}); ok { + return v.(*AccountInfo), nil + } + + // check if the account cache is evicted. if yes we need + // fetch the record from the contract + if a.evicted { + // call function to populate cache with the record + acctRec, err := a.populateCacheFunc(acct) + // insert the received record into cache + if err != nil { + return nil, err + } + a.UpsertAccount(acctRec.OrgId, acctRec.RoleId, acctRec.AcctId, acctRec.IsOrgAdmin, acctRec.Status) + //return the record + return acctRec, nil + } + return nil, nil +} + +func (a *AcctCache) GetAcctList() []AccountInfo { + alist := make([]AccountInfo, len(a.c.Keys())) + for i, k := range a.c.Keys() { + v, _ := a.c.Get(k) + vp := v.(*AccountInfo) + alist[i] = *vp + } + return alist +} + +func (a *AcctCache) GetAcctListOrg(orgId string) []AccountInfo { + var alist []AccountInfo + for _, k := range a.c.Keys() { + v, _ := a.c.Get(k) + vp := v.(*AccountInfo) + if vp.OrgId == orgId { + alist = append(alist, *vp) + } + } + return alist +} + +func (a *AcctCache) GetAcctListRole(orgId, roleId string) []AccountInfo { + var alist []AccountInfo + for _, k := range a.c.Keys() { + v, _ := a.c.Get(k) + vp := v.(*AccountInfo) + + orgRec, err := OrgInfoMap.GetOrg(vp.OrgId) + if err != nil { + return nil + } + + if vp.RoleId == roleId && (vp.OrgId == orgId || (orgRec != nil && orgRec.UltimateParent == orgId)) { + alist = append(alist, *vp) + } + } + return alist +} + +func (r *RoleCache) UpsertRole(orgId string, role string, voter bool, admin bool, access AccessType, active bool) { + key := RoleKey{orgId, role} + r.c.Add(key, &RoleInfo{orgId, role, voter, admin, access, active}) + +} + +func (r *RoleCache) GetRole(orgId string, roleId string) (*RoleInfo, error) { + key := RoleKey{OrgId: orgId, RoleId: roleId} + if ent, ok := r.c.Get(key); ok { + return ent.(*RoleInfo), nil + } + // check if the role cache is evicted. if yes we need + // fetch the record from the contract + if r.evicted { + // call cache population function to populate from contract + roleRec, err := r.populateCacheFunc(&RoleKey{RoleId: roleId, OrgId: orgId}) + if err != nil { + return nil, err + } + // insert the received record into cache + r.UpsertRole(roleRec.OrgId, roleRec.RoleId, roleRec.IsVoter, roleRec.IsAdmin, roleRec.Access, roleRec.Active) + + //return the record + return roleRec, nil + } + return nil, errors.New("Invalid role") +} + +func (r *RoleCache) GetRoleList() []RoleInfo { + rlist := make([]RoleInfo, len(r.c.Keys())) + for i, k := range r.c.Keys() { + v, _ := r.c.Get(k) + vp := v.(*RoleInfo) + rlist[i] = *vp + } + return rlist +} + +// Returns the access type for an account. If not found returns +// default access +func GetAcctAccess(acctId common.Address) AccessType { + + // check if the org status is fine to do the transaction + a, _ := AcctInfoMap.GetAccount(acctId) + if a != nil && a.Status == AcctActive { + // get the org details and ultimate org details. check org status + // if the org is not approved or pending suspension + if checkIfOrgActive(a.OrgId) { + if a.RoleId == networkAdminRole || a.RoleId == orgAdminRole { + return FullAccess + } + if r, _ := RoleInfoMap.GetRole(a.OrgId, a.RoleId); r != nil && r.Active { + return r.Access + } + if o, _ := OrgInfoMap.GetOrg(a.OrgId); o != nil { + if r, _ := RoleInfoMap.GetRole(o.UltimateParent, a.RoleId); r != nil && r.Active { + return r.Access + } + } + } + } + return defaultAccess +} + +//checks if the given org is active in the network +func checkIfOrgActive(orgId string) bool { + o, _ := OrgInfoMap.GetOrg(orgId) + if o != nil && o.Status != OrgSuspended { + u, _ := OrgInfoMap.GetOrg(o.UltimateParent) + if u == nil { + return true + } + if u != nil && u.Status != OrgSuspended { + return true + } + } + return false +} + +// checks if the passed account is linked to a org admin or +// network admin role +func CheckIfAdminAccount(acctId common.Address) bool { + if !PermissionsEnabled() { + return true + } + a, _ := AcctInfoMap.GetAccount(acctId) + if a != nil && a.Status == AcctActive { + if checkIfOrgActive(a.OrgId) { + if a.RoleId == networkAdminRole || a.RoleId == orgAdminRole { + return true + } + if r, _ := RoleInfoMap.GetRole(a.OrgId, a.RoleId); r != nil && r.Active && r.IsAdmin { + return true + } + if o, _ := OrgInfoMap.GetOrg(a.OrgId); o != nil { + if r, _ := RoleInfoMap.GetRole(o.UltimateParent, a.RoleId); r != nil && r.Active && r.IsAdmin { + return true + } + } + } + } + return false +} + +// validates if the account can transact from the current node +func ValidateNodeForTxn(hexnodeId string, from common.Address) bool { + if !PermissionsEnabled() || hexnodeId == "" { + return true + } + + passedEnodeId, err := enode.ParseV4(hexnodeId) + if err != nil { + return false + } + + ac, _ := AcctInfoMap.GetAccount(from) + if ac == nil { + return true + } + + acOrgRec, err := OrgInfoMap.GetOrg(ac.OrgId) + if err != nil { + return false + } + + // scan through the node list and validate + for _, n := range NodeInfoMap.GetNodeList() { + orgRec, err := OrgInfoMap.GetOrg(n.OrgId) + if err != nil { + return false + } + if orgRec.UltimateParent == acOrgRec.UltimateParent { + recEnodeId, _ := enode.ParseV4(n.Url) + if recEnodeId.ID() == passedEnodeId.ID() && n.Status == NodeApproved { + return true + } + } + } + if NodeInfoMap.evicted { + return NodeInfoMap.populateAndValidateFunc(hexnodeId, acOrgRec.UltimateParent) + } + + return false +} + +func IsV2Permission() bool { + return PermissionModel == V2 +} + +// checks if the account permission allows the transaction to be executed +func IsTransactionAllowed(from common.Address, to common.Address, value *big.Int, gasPrice *big.Int, gasLimit *big.Int, payload []byte, transactionType TransactionType) error { + //if we have not reached QIP714 block return full access + if !PermissionsEnabled() { + return nil + } + + return PermissionTransactionAllowedFunc(from, to, value, gasPrice, gasLimit, payload, transactionType) +} diff --git a/permission/core/cache_test.go b/permission/core/cache_test.go new file mode 100644 index 0000000000..98f4cc5f8d --- /dev/null +++ b/permission/core/cache_test.go @@ -0,0 +1,433 @@ +package core + +import ( + "fmt" + "math/big" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + testifyassert "github.com/stretchr/testify/assert" +) + +var ( + NETWORKADMIN = "NWADMIN" + ORGADMIN = "OADMIN" + NODE1 = "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@127.0.0.1:21000?discport=0&raftport=50401" + NODE2 = "enode://0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416@127.0.0.1:21001?discport=0&raftport=50402" +) + +var Acct1 = common.BytesToAddress([]byte("permission")) +var Acct2 = common.BytesToAddress([]byte("perm-test")) + +func TestSetSyncStatus(t *testing.T) { + assert := testifyassert.New(t) + + SetSyncStatus() + + // check if the value is set properly by calling Get + syncStatus := GetSyncStatus() + assert.True(syncStatus, fmt.Sprintf("Expected syncstatus %v . Got %v ", true, syncStatus)) +} + +func TestSetDefaults(t *testing.T) { + assert := testifyassert.New(t) + + SetDefaults(NETWORKADMIN, ORGADMIN, false) + + // get the default values and confirm the same + networkAdminRole, orgAdminRole, defaultAccess := GetDefaults() + + assert.True(networkAdminRole == NETWORKADMIN, fmt.Sprintf("Expected network admin role %v, got %v", NETWORKADMIN, networkAdminRole)) + assert.True(orgAdminRole == ORGADMIN, fmt.Sprintf("Expected network admin role %v, got %v", ORGADMIN, orgAdminRole)) + assert.True(defaultAccess == FullAccess, fmt.Sprintf("Expected network admin role %v, got %v", FullAccess, defaultAccess)) + + SetNetworkBootUpCompleted() + SetQIP714BlockReached() + _, _, defaultAccess = GetDefaults() + assert.True(defaultAccess == ReadOnly, fmt.Sprintf("Expected network admin role %v, got %v", ReadOnly, defaultAccess)) +} + +func TestOrgCache_UpsertOrg(t *testing.T) { + assert := testifyassert.New(t) + + OrgInfoMap = NewOrgCache(params.DEFAULT_ORGCACHE_SIZE) + + //add a org and get the org details + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + orgInfo, err := OrgInfoMap.GetOrg(NETWORKADMIN) + assert.True(err == nil, "errors encountered") + + assert.False(orgInfo == nil, "Expected org details, got nil") + assert.True(orgInfo.OrgId == NETWORKADMIN, fmt.Sprintf("Expected org id %v, got %v", NETWORKADMIN, orgInfo.OrgId)) + + // update org status to suspended + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgSuspended) + orgInfo, err = OrgInfoMap.GetOrg(NETWORKADMIN) + assert.True(err == nil, "errors encountered") + + assert.True(orgInfo.Status == OrgSuspended, fmt.Sprintf("Expected org status %v, got %v", OrgSuspended, orgInfo.Status)) + + //add another org and check get org list + OrgInfoMap.UpsertOrg(ORGADMIN, "", ORGADMIN, big.NewInt(1), OrgApproved) + orgList := OrgInfoMap.GetOrgList() + assert.True(len(orgList) == 2, fmt.Sprintf("Expected 2 entries, got %v", len(orgList))) + + //add sub org and check get orglist + OrgInfoMap.UpsertOrg("SUB1", ORGADMIN, ORGADMIN, big.NewInt(2), OrgApproved) + orgList = OrgInfoMap.GetOrgList() + assert.True(len(orgList) == 3, fmt.Sprintf("Expected 3 entries, got %v", len(orgList))) + + //suspend the sub org and check get orglist + OrgInfoMap.UpsertOrg("SUB1", ORGADMIN, ORGADMIN, big.NewInt(2), OrgSuspended) + orgList = OrgInfoMap.GetOrgList() + assert.True(len(orgList) == 3, fmt.Sprintf("Expected 3 entries, got %v", len(orgList))) +} + +func TestNodeCache_UpsertNode(t *testing.T) { + assert := testifyassert.New(t) + + NodeInfoMap = NewNodeCache(params.DEFAULT_NODECACHE_SIZE) + + // add a node into the cache and validate + NodeInfoMap.UpsertNode(NETWORKADMIN, NODE1, NodeApproved) + nodeInfo, err := NodeInfoMap.GetNodeByUrl(NODE1) + assert.True(err == nil, "got errors in node fetch") + + assert.False(nodeInfo == nil, "Expected node details, got nil") + assert.True(nodeInfo.OrgId == NETWORKADMIN, fmt.Sprintf("Expected org id for node %v, got %v", NETWORKADMIN, nodeInfo.OrgId)) + assert.True(nodeInfo.Url == NODE1, fmt.Sprintf("Expected node id %v, got %v", NODE1, nodeInfo.Url)) + + // add another node and validate the list function + NodeInfoMap.UpsertNode(ORGADMIN, NODE2, NodeApproved) + nodeList := NodeInfoMap.GetNodeList() + assert.True(len(nodeList) == 2, fmt.Sprintf("Expected 2 entries, got %v", len(nodeList))) + + // check node details update by updating node status + NodeInfoMap.UpsertNode(ORGADMIN, NODE2, NodeDeactivated) + nodeInfo, err = NodeInfoMap.GetNodeByUrl(NODE2) + assert.True(err == nil, "got errors in node fetch") + + assert.True(nodeInfo.Status == NodeDeactivated, fmt.Sprintf("Expected node status %v, got %v", NodeDeactivated, nodeInfo.Status)) +} + +func TestRoleCache_UpsertRole(t *testing.T) { + assert := testifyassert.New(t) + + RoleInfoMap = NewRoleCache(params.DEFAULT_ROLECACHE_SIZE) + + // add a role into the cache and validate + RoleInfoMap.UpsertRole(NETWORKADMIN, NETWORKADMIN, true, true, FullAccess, true) + roleInfo, err := RoleInfoMap.GetRole(NETWORKADMIN, NETWORKADMIN) + assert.True(err == nil, "errors encountered") + assert.False(roleInfo == nil, "Expected role details, got nil") + assert.True(roleInfo.OrgId == NETWORKADMIN, fmt.Sprintf("Expected org id for node %v, got %v", NETWORKADMIN, roleInfo.OrgId)) + assert.True(roleInfo.RoleId == NETWORKADMIN, fmt.Sprintf("Expected node id %v, got %v", NETWORKADMIN, roleInfo.RoleId)) + + // add another role and validate the list function + RoleInfoMap.UpsertRole(ORGADMIN, ORGADMIN, true, true, FullAccess, true) + roleList := RoleInfoMap.GetRoleList() + assert.True(len(roleList) == 2, fmt.Sprintf("Expected 2 entries, got %v", len(roleList))) + + // update role status and validate + RoleInfoMap.UpsertRole(ORGADMIN, ORGADMIN, true, true, FullAccess, false) + roleInfo, err = RoleInfoMap.GetRole(ORGADMIN, ORGADMIN) + assert.True(err == nil, "errors encountered") + + assert.True(!roleInfo.Active, fmt.Sprintf("Expected role active status to be %v, got %v", true, roleInfo.Active)) +} + +func TestAcctCache_UpsertAccount(t *testing.T) { + assert := testifyassert.New(t) + + AcctInfoMap = NewAcctCache(params.DEFAULT_ACCOUNTCACHE_SIZE) + + // add an account into the cache and validate + AcctInfoMap.UpsertAccount(NETWORKADMIN, NETWORKADMIN, Acct1, true, AcctActive) + acctInfo, err := AcctInfoMap.GetAccount(Acct1) + assert.True(err == nil) + + assert.False(acctInfo == nil, "Expected account details, got nil") + assert.True(acctInfo.OrgId == NETWORKADMIN, fmt.Sprintf("Expected org id for the account to be %v, got %v", NETWORKADMIN, acctInfo.OrgId)) + assert.True(acctInfo.AcctId == Acct1, fmt.Sprintf("Expected account id %x, got %x", Acct1, acctInfo.AcctId)) + + // add a second account and validate the list function + AcctInfoMap.UpsertAccount(ORGADMIN, ORGADMIN, Acct2, true, AcctActive) + acctList := AcctInfoMap.GetAcctList() + assert.True(len(acctList) == 2, fmt.Sprintf("Expected 2 entries, got %v", len(acctList))) + + // update account status and validate + AcctInfoMap.UpsertAccount(ORGADMIN, ORGADMIN, Acct2, true, AcctBlacklisted) + acctInfo, err = AcctInfoMap.GetAccount(Acct2) + assert.True(err == nil) + + assert.True(acctInfo.Status == AcctBlacklisted, fmt.Sprintf("Expected account status to be %v, got %v", AcctBlacklisted, acctInfo.Status)) + + // validate the list for org and role functions + acctList = AcctInfoMap.GetAcctListOrg(NETWORKADMIN) + assert.True(len(acctList) == 1, fmt.Sprintf("Expected number of accounts for the org to be 1, got %v", len(acctList))) + acctList = AcctInfoMap.GetAcctListRole(NETWORKADMIN, NETWORKADMIN) + assert.True(len(acctList) == 1, fmt.Sprintf("Expected number of accounts for the role to be 1, got %v", len(acctList))) +} + +func TestGetAcctAccess(t *testing.T) { + assert := testifyassert.New(t) + + // default access when the cache is not populated, should return default access + SetDefaults(NETWORKADMIN, ORGADMIN, false) + SetQIP714BlockReached() + SetNetworkBootUpCompleted() + access := GetAcctAccess(Acct1) + assert.True(access == ReadOnly, fmt.Sprintf("Expected account access to be %v, got %v", ReadOnly, access)) + + // Create an org with two roles and two accounts linked to different roles. Validate account access + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + RoleInfoMap.UpsertRole(NETWORKADMIN, NETWORKADMIN, true, true, FullAccess, true) + RoleInfoMap.UpsertRole(NETWORKADMIN, "ROLE1", true, true, FullAccess, true) + AcctInfoMap.UpsertAccount(NETWORKADMIN, NETWORKADMIN, Acct1, true, AcctActive) + AcctInfoMap.UpsertAccount(NETWORKADMIN, "ROLE1", Acct2, true, AcctActive) + + access = GetAcctAccess(Acct1) + assert.True(access == FullAccess, fmt.Sprintf("Expected account access to be %v, got %v", FullAccess, access)) + + // mark the org as pending suspension. The account access should not change + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgPendingSuspension) + access = GetAcctAccess(Acct1) + assert.True(access == FullAccess, fmt.Sprintf("Expected account access to be %v, got %v", FullAccess, access)) + + // suspend the org and the account access should be readonly now + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgSuspended) + access = GetAcctAccess(Acct1) + assert.True(access == ReadOnly, fmt.Sprintf("Expected account access to be %v, got %v", ReadOnly, access)) + + // mark the role as inactive and account access should now nbe read only + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + RoleInfoMap.UpsertRole(NETWORKADMIN, "ROLE1", true, true, FullAccess, false) + access = GetAcctAccess(Acct2) + assert.True(access == ReadOnly, fmt.Sprintf("Expected account access to be %v, got %v", ReadOnly, access)) +} + +func TestValidateNodeForTxn(t *testing.T) { + assert := testifyassert.New(t) + // pass the enode as null and the response should be true + txnAllowed := ValidateNodeForTxn("", Acct1) + assert.True(txnAllowed, "Expected access %v, got %v", true, txnAllowed) + + SetQIP714BlockReached() + SetNetworkBootUpCompleted() + // if a proper enode id is not passed, return should be false + txnAllowed = ValidateNodeForTxn("ABCDE", Acct1) + assert.True(!txnAllowed, "Expected access %v, got %v", true, txnAllowed) + + // if cache is not populated but the enode and account details are proper, + // should return true + txnAllowed = ValidateNodeForTxn(NODE1, Acct1) + assert.True(txnAllowed, "Expected access %v, got %v", true, txnAllowed) + + // populate an org, account and node. validate access + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + NodeInfoMap.UpsertNode(NETWORKADMIN, NODE1, NodeApproved) + AcctInfoMap.UpsertAccount(NETWORKADMIN, NETWORKADMIN, Acct1, true, AcctActive) + txnAllowed = ValidateNodeForTxn(NODE1, Acct1) + assert.True(txnAllowed, "Expected access %v, got %v", true, txnAllowed) + + // test access from a node not linked to the org. should return false + OrgInfoMap.UpsertOrg(ORGADMIN, "", ORGADMIN, big.NewInt(1), OrgApproved) + NodeInfoMap.UpsertNode(ORGADMIN, NODE2, NodeApproved) + AcctInfoMap.UpsertAccount(ORGADMIN, ORGADMIN, Acct2, true, AcctActive) + txnAllowed = ValidateNodeForTxn(NODE1, Acct2) + assert.True(!txnAllowed, "Expected access %v, got %v", true, txnAllowed) +} + +// This is to make sure enode.ParseV4() honors single hexNodeId value eventhough it does follow enode URI scheme +func TestValidateNodeForTxn_whenUsingOnlyHexNodeId(t *testing.T) { + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + NodeInfoMap.UpsertNode(NETWORKADMIN, NODE1, NodeApproved) + AcctInfoMap.UpsertAccount(NETWORKADMIN, NETWORKADMIN, Acct1, true, AcctActive) + arbitraryPrivateKey, _ := crypto.GenerateKey() + hexNodeId := fmt.Sprintf("%x", crypto.FromECDSAPub(&arbitraryPrivateKey.PublicKey)[1:]) + + SetQIP714BlockReached() + SetNetworkBootUpCompleted() + + txnAllowed := ValidateNodeForTxn(hexNodeId, Acct1) + + testifyassert.False(t, txnAllowed) +} + +// test the cache limit +func TestLRUCacheLimit(t *testing.T) { + for i := 0; i < params.DEFAULT_ORGCACHE_SIZE; i++ { + orgName := "ORG" + strconv.Itoa(i) + OrgInfoMap.UpsertOrg(orgName, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + } + + o, err := OrgInfoMap.GetOrg("ORG1") + testifyassert.True(t, err == nil) + testifyassert.True(t, o != nil) +} + +func TestCheckIfAdminAccount(t *testing.T) { + SetDefaults(NETWORKADMIN, ORGADMIN, false) + SetQIP714BlockReached() + SetQIP714BlockReached() + + var Acct3 = common.BytesToAddress([]byte("permission-test1")) + var Acct4 = common.BytesToAddress([]byte("permission-test2")) + var Acct5 = common.BytesToAddress([]byte("permission-test3")) + var Acct6 = common.BytesToAddress([]byte("permission-test4")) + var Acct7 = common.BytesToAddress([]byte("permission-test5")) + var Acct8 = common.BytesToAddress([]byte("permission-test6")) + var Acct9 = common.BytesToAddress([]byte("unassigned-account")) + + // Create two orgs, Networkadmin and OADMIN + OrgInfoMap.UpsertOrg(NETWORKADMIN, "", NETWORKADMIN, big.NewInt(1), OrgApproved) + OrgInfoMap.UpsertOrg(ORGADMIN, "", ORGADMIN, big.NewInt(1), OrgApproved) + + // Insert roles for both orgs one being admin role and the other a normal role + RoleInfoMap.UpsertRole(NETWORKADMIN, NETWORKADMIN, true, true, FullAccess, true) + RoleInfoMap.UpsertRole(NETWORKADMIN, "ROLE1", true, false, Transact, true) + RoleInfoMap.UpsertRole(NETWORKADMIN, "ROLE2", true, true, Transact, false) + + RoleInfoMap.UpsertRole(ORGADMIN, ORGADMIN, true, true, FullAccess, true) + RoleInfoMap.UpsertRole(ORGADMIN, "ROLE1", true, false, Transact, true) + RoleInfoMap.UpsertRole(ORGADMIN, "ROLE2", true, true, Transact, false) + + // Assign accounts to orgs + AcctInfoMap.UpsertAccount(NETWORKADMIN, NETWORKADMIN, Acct1, true, AcctActive) + AcctInfoMap.UpsertAccount(NETWORKADMIN, "ROLE1", Acct2, false, AcctActive) + AcctInfoMap.UpsertAccount(NETWORKADMIN, "ROLE2", Acct3, true, AcctActive) + AcctInfoMap.UpsertAccount(NETWORKADMIN, NETWORKADMIN, Acct4, true, AcctBlacklisted) + + AcctInfoMap.UpsertAccount(ORGADMIN, ORGADMIN, Acct5, true, AcctActive) + AcctInfoMap.UpsertAccount(ORGADMIN, "ROLE1", Acct6, false, AcctActive) + AcctInfoMap.UpsertAccount(ORGADMIN, "ROLE2", Acct7, true, AcctActive) + AcctInfoMap.UpsertAccount(ORGADMIN, ORGADMIN, Acct8, true, AcctBlacklisted) + + type args struct { + acctId common.Address + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Network admin account", + args: args{Acct1}, + want: true, + }, + { + name: "Normal account in Network admin org", + args: args{Acct2}, + want: false, + }, + { + name: "Account linked to an inactive org admin role - network admin org", + args: args{Acct2}, + want: false, + }, + { + name: "Network admin account which is blacklisted", + args: args{Acct4}, + want: false, + }, + { + name: "Org admin account", + args: args{Acct5}, + want: true, + }, + { + name: "Normal account in in org", + args: args{Acct6}, + want: false, + }, + { + name: "Account linked to an inactive org admin role in org", + args: args{Acct7}, + want: false, + }, + { + name: "org admin account which is blacklisted", + args: args{Acct8}, + want: false, + }, + { + name: "Unassigned account", + args: args{Acct9}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CheckIfAdminAccount(tt.args.acctId); got != tt.want { + t.Errorf("CheckIfAdminAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_checkIfOrgActive(t *testing.T) { + OrgInfoMap = NewOrgCache(params.DEFAULT_ORGCACHE_SIZE) + OrgInfoMap.UpsertOrg("ORG1", "", "ORG1", big.NewInt(1), OrgApproved) + OrgInfoMap.UpsertOrg("ORG2", "", "ORG2", big.NewInt(1), OrgPendingSuspension) + OrgInfoMap.UpsertOrg("ORG3", "ORG1", "ORG1", big.NewInt(2), OrgApproved) + OrgInfoMap.UpsertOrg("ORG4", "ORG2", "ORG2", big.NewInt(2), OrgApproved) + OrgInfoMap.UpsertOrg("ORG5", "", "ORG5", big.NewInt(1), OrgSuspended) + OrgInfoMap.UpsertOrg("ORG6", "ORG5", "ORG5", big.NewInt(2), OrgApproved) + OrgInfoMap.UpsertOrg("ORG7", "ORG5", "ORG5", big.NewInt(2), OrgSuspended) + + type args struct { + orgId string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Org is approved", + args: args{orgId: "ORG1"}, + want: true, + }, + { + name: "Org under suspension", + args: args{orgId: "ORG2"}, + want: true, + }, + { + name: "Sub org approved", + args: args{orgId: "ORG1.ORG3"}, + want: true, + }, + { + name: "Sub org approved under a pending suspension org", + args: args{orgId: "ORG2.ORG4"}, + want: true, + }, + { + name: "Org suspended", + args: args{orgId: "ORG5"}, + want: false, + }, + { + name: "Approved sub org under a suspended org", + args: args{orgId: "ORG5.ORG6"}, + want: false, + }, + { + name: "Suspended sub org under a suspended org", + args: args{orgId: "ORG5.ORG7"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checkIfOrgActive(tt.args.orgId); got != tt.want { + t.Errorf("checkIfOrgActive() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/permission/core/permissions.go b/permission/core/permissions.go new file mode 100644 index 0000000000..9eaf4af08e --- /dev/null +++ b/permission/core/permissions.go @@ -0,0 +1,127 @@ +package core + +import ( + "encoding/json" + "io/ioutil" + "math/big" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" +) + +// check if a given node is permissioned to connect to the change +func IsNodePermissioned(nodename string, currentNode string, datadir string, direction string) bool { + var permissionedList []string + nodes := ParsePermissionedNodes(datadir) + for _, v := range nodes { + permissionedList = append(permissionedList, v.ID().String()) + } + + log.Debug("IsNodePermissioned", "permissionedList", permissionedList) + for _, v := range permissionedList { + if v == nodename { + log.Debug("IsNodePermissioned", "connection", direction, "nodename", nodename[:params.NODE_NAME_LENGTH], "ALLOWED-BY", currentNode[:params.NODE_NAME_LENGTH]) + // check if the node is blacklisted + return !isNodeBlackListed(nodename, datadir) + } + } + log.Debug("IsNodePermissioned", "connection", direction, "nodename", nodename[:params.NODE_NAME_LENGTH], "DENIED-BY", currentNode[:params.NODE_NAME_LENGTH]) + return false +} + +//this is a shameless copy from the config.go. It is a duplication of the code +//for the timebeing to allow reload of the permissioned nodes while the server is running + +func ParsePermissionedNodes(DataDir string) []*enode.Node { + + log.Debug("parsePermissionedNodes", "DataDir", DataDir, "file", params.PERMISSIONED_CONFIG) + + path := filepath.Join(DataDir, params.PERMISSIONED_CONFIG) + if _, err := os.Stat(path); err != nil { + log.Error("Read Error for permissioned-nodes.json file. This is because 'permissioned' flag is specified but no permissioned-nodes.json file is present.", "err", err) + return nil + } + // Load the nodes from the config file + blob, err := ioutil.ReadFile(path) + if err != nil { + log.Error("parsePermissionedNodes: Failed to access nodes", "err", err) + return nil + } + + nodelist := []string{} + if err := json.Unmarshal(blob, &nodelist); err != nil { + log.Error("parsePermissionedNodes: Failed to load nodes", "err", err) + return nil + } + // Interpret the list as a discovery node array + var nodes []*enode.Node + for _, url := range nodelist { + if url == "" { + log.Error("parsePermissionedNodes: Node URL blank") + continue + } + node, err := enode.ParseV4(url) + if err != nil { + log.Error("parsePermissionedNodes: Node URL", "url", url, "err", err) + continue + } + nodes = append(nodes, node) + } + return nodes +} + +// This function checks if the node is black-listed +func isNodeBlackListed(nodeName, dataDir string) bool { + log.Debug("isNodeBlackListed", "DataDir", dataDir, "file", params.BLACKLIST_CONFIG) + + path := filepath.Join(dataDir, params.BLACKLIST_CONFIG) + if _, err := os.Stat(path); err != nil { + log.Debug("Read Error for disallowed-nodes.json file. disallowed-nodes.json file is not present.", "err", err) + return false + } + // Load the nodes from the config file + blob, err := ioutil.ReadFile(path) + if err != nil { + log.Debug("isNodeBlackListed: Failed to access nodes", "err", err) + return true + } + + nodelist := []string{} + if err := json.Unmarshal(blob, &nodelist); err != nil { + log.Debug("parsePermissionedNodes: Failed to load nodes", "err", err) + return true + } + + for _, v := range nodelist { + n, _ := enode.ParseV4(v) + if nodeName == n.ID().String() { + return true + } + } + return false +} + +// function checks for account access to execute the transaction +func CheckAccountPermission(from common.Address, to *common.Address, value *big.Int, data []byte, gas uint64, gasPrice *big.Int) error { + transactionType := ValueTransferTxn + + if to == nil { + transactionType = ContractDeployTxn + } else if data != nil { + transactionType = ContractCallTxn + } + + var toAcct common.Address + + if to == nil { + toAcct = common.Address{} + } else { + toAcct = *to + } + + return IsTransactionAllowed(from, toAcct, value, gasPrice, big.NewInt(int64(gas)), data, transactionType) +} diff --git a/permission/core/permissions_test.go b/permission/core/permissions_test.go new file mode 100644 index 0000000000..dd04d76833 --- /dev/null +++ b/permission/core/permissions_test.go @@ -0,0 +1,132 @@ +package core + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" +) + +const ( + node1 = "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@127.0.0.1:21000?discport=0&raftport=50401" + node2 = "enode://0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416@127.0.0.1:21001?discport=0&raftport=50402" + node3 = "enode://579f786d4e2830bbcc02815a27e8a9bacccc9605df4dc6f20bcc1a6eb391e7225fff7cb83e5b4ecd1f3a94d8b733803f2f66b7e871961e7b029e22c155c3a778@127.0.0.1:21002?discport=0&raftport=50403" +) + +func TestIsNodePermissioned(t *testing.T) { + type args struct { + nodename string + currentNode string + datadir string + direction string + } + d, _ := ioutil.TempDir("", "qdata") + defer os.RemoveAll(d) + writeNodeToFile(d, params.PERMISSIONED_CONFIG, node1) + writeNodeToFile(d, params.PERMISSIONED_CONFIG, node3) + writeNodeToFile(d, params.BLACKLIST_CONFIG, node3) + n1, _ := enode.ParseV4(node1) + n2, _ := enode.ParseV4(node2) + n3, _ := enode.ParseV4(node3) + + tests := []struct { + name string + args args + want bool + }{ + { + name: "node present", + args: args{n1.ID().String(), n2.EnodeID(), d, "INWARD"}, + want: true, + }, + { + name: "node not present", + args: args{n2.ID().String(), n1.EnodeID(), d, "OUTWARD"}, + want: false, + }, + { + name: "blacklisted node", + args: args{n3.ID().String(), n1.EnodeID(), d, "INWARD"}, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsNodePermissioned(tt.args.nodename, tt.args.currentNode, tt.args.datadir, tt.args.direction); got != tt.want { + t.Errorf("IsNodePermissioned() = %v, want %v", got, tt.want) + } + }) + } + +} + +func Test_isNodeBlackListed(t *testing.T) { + type args struct { + nodeName string + dataDir string + } + + d, _ := ioutil.TempDir("", "qdata") + defer os.RemoveAll(d) + writeNodeToFile(d, params.BLACKLIST_CONFIG, node1) + n1, _ := enode.ParseV4(node1) + n2, _ := enode.ParseV4(node2) + + tests := []struct { + name string + args args + want bool + }{ + { + name: "blacklisted node", + args: args{n1.ID().String(), d}, + want: true, + }, + { + name: "blacklisted node", + args: args{n2.ID().String(), d}, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isNodeBlackListed(tt.args.nodeName, tt.args.dataDir); got != tt.want { + t.Errorf("isNodeBlackListed() = %v, want %v", got, tt.want) + } + }) + } +} + +func writeNodeToFile(dataDir, fileName, url string) { + fileExists := true + path := filepath.Join(dataDir, fileName) + + // Check if the file is existing. If the file is not existing create the file + if _, err := os.Stat(path); err != nil { + if _, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644); err != nil { + return + } + fileExists = false + } + + var nodeList []string + var blob []byte + if fileExists { + blob, err := ioutil.ReadFile(path) + if err == nil { + if err := json.Unmarshal(blob, &nodeList); err != nil { + return + } + } + } + nodeList = append(nodeList, url) + blob, _ = json.Marshal(nodeList) + _ = ioutil.WriteFile(path, blob, 0644) + +} diff --git a/permission/core/types/backend.go b/permission/core/types/backend.go new file mode 100644 index 0000000000..a5c77692bf --- /dev/null +++ b/permission/core/types/backend.go @@ -0,0 +1,350 @@ +package types + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/raft" +) + +// supports 2 models of permissions v1 and v2. +// v2 is aligned with the latest eea specs +const ( + PERMISSION_V1 = "v1" + PERMISSION_V2 = "v2" +) + +// permission config for bootstrapping +type PermissionConfig struct { + PermissionsModel string `json:"permissionModel"` + UpgrdAddress common.Address `json:"upgrdableAddress"` + InterfAddress common.Address `json:"interfaceAddress"` + ImplAddress common.Address `json:"implAddress"` + NodeAddress common.Address `json:"nodeMgrAddress"` + AccountAddress common.Address `json:"accountMgrAddress"` + RoleAddress common.Address `json:"roleMgrAddress"` + VoterAddress common.Address `json:"voterMgrAddress"` + OrgAddress common.Address `json:"orgMgrAddress"` + NwAdminOrg string `json:"nwAdminOrg"` + NwAdminRole string `json:"nwAdminRole"` + OrgAdminRole string `json:"orgAdminRole"` + + Accounts []common.Address `json:"accounts"` //initial list of account that need full access + SubOrgDepth *big.Int `json:"subOrgDepth"` + SubOrgBreadth *big.Int `json:"subOrgBreadth"` +} + +var ( + ErrInvalidInput = errors.New("Invalid input") + ErrInvalidRole = errors.New("Invalid role") + ErrNotNetworkAdmin = errors.New("Operation can be performed by network admin only. Account not a network admin.") + ErrNotOrgAdmin = errors.New("Operation can be performed by org admin only. Account not a org admin.") + ErrNodePresent = errors.New("EnodeId already part of network.") + ErrInvalidNode = errors.New("Invalid enode id") + ErrInvalidAccount = errors.New("Invalid account id") + ErrOrgExists = errors.New("Org already exist") + ErrPendingApprovals = errors.New("Pending approvals for the organization. Approve first") + ErrNothingToApprove = errors.New("Nothing to approve") + ErrOpNotAllowed = errors.New("Operation not allowed") + ErrNodeOrgMismatch = errors.New("Enode id passed does not belong to the organization.") + ErrBlacklistedNode = errors.New("Blacklisted node. Operation not allowed") + ErrBlacklistedAccount = errors.New("Blacklisted account. Operation not allowed") + ErrAccountOrgAdmin = errors.New("Account already org admin for the org") + ErrOrgAdminExists = errors.New("Org admin exist for the org") + ErrAccountInUse = errors.New("Account already in use in another organization") + ErrRoleExists = errors.New("Role exist for the org") + ErrRoleActive = errors.New("Accounts linked to the role. Cannot be removed") + ErrAdminRoles = errors.New("Admin role cannot be removed") + ErrInvalidOrgName = errors.New("Org id cannot contain special characters") + ErrInvalidParentOrg = errors.New("Invalid parent org id") + ErrAccountNotThere = errors.New("Account does not exist") + ErrOrgNotOwner = errors.New("Account does not belong to this org") + ErrMaxDepth = errors.New("Max depth for sub orgs reached") + ErrMaxBreadth = errors.New("Max breadth for sub orgs reached") + ErrNodeDoesNotExists = errors.New("Node does not exist") + ErrOrgDoesNotExists = errors.New("Org does not exist") + ErrInactiveRole = errors.New("Role is already inactive") + + ErrNotMasterOrg = errors.New("Org is not a master org") + ErrHostNameNotSupported = errors.New("Hostname not supported in the network") + ErrNoPermissionForTxn = errors.New("account does not have permission for the transaction") +) + +// backend struct for interfaces +type InterfaceBackend struct { + node *node.Node + isRaft bool + dataDir string +} + +func (i *InterfaceBackend) SetIsRaft(isRaft bool) { + i.isRaft = isRaft +} + +func NewInterfaceBackend(node *node.Node, isRaft bool, dataDir string) *InterfaceBackend { + return &InterfaceBackend{node: node, isRaft: isRaft, dataDir: dataDir} +} + +func (i InterfaceBackend) Node() *node.Node { + return i.node +} + +func (i InterfaceBackend) IsRaft() bool { + return i.isRaft +} + +func (i InterfaceBackend) DataDir() string { + return i.dataDir +} + +// to signal all watches when service is stopped +type StopEvent struct { +} + +// broadcasting stopEvent when service is being stopped +var StopFeed event.Feed +var mux sync.Mutex + +type NodeOperation uint8 + +const ( + NodeAdd NodeOperation = iota + NodeDelete +) + +type Backend interface { + // role service for role management service + GetRoleService(transactOpts *bind.TransactOpts, roleBackend ContractBackend) (RoleService, error) + // org service for org management service + GetOrgService(transactOpts *bind.TransactOpts, orgBackend ContractBackend) (OrgService, error) + // node service for node management service + GetNodeService(transactOpts *bind.TransactOpts, nodeBackend ContractBackend) (NodeService, error) + // account service for account management service + GetAccountService(transactOpts *bind.TransactOpts, accountBackend ContractBackend) (AccountService, error) + // audit service for account management service + GetAuditService(auditBackend ContractBackend) (AuditService, error) + // control service for account management service + GetControlService(controlBackend ContractBackend) (ControlService, error) + // Monitors account access related events and updates the cache accordingly + ManageAccountPermissions() error + // Monitors Node management events and updates cache accordingly + ManageNodePermissions() error + // monitors org management related events happening via smart contracts + // and updates cache accordingly + ManageOrgPermissions() error + // monitors role management related events and updated cache + ManageRolePermissions() error + + // monitors for network boot up complete event + MonitorNetworkBootUp() error +} + +// adds or deletes and entry from a given file +func UpdateFile(fileName, enodeId string, operation NodeOperation, createFile bool) error { + // Load the nodes from the config file + var nodeList []string + index := 0 + // if createFile is false means the file is already existing. read the file + if !createFile { + blob, err := ioutil.ReadFile(fileName) + if err != nil && !createFile { + return err + } + + if err := json.Unmarshal(blob, &nodeList); err != nil { + return err + } + + // logic to update the permissioned-nodes.json file based on action + + recExists := false + for i, eid := range nodeList { + if eid == enodeId { + index = i + recExists = true + break + } + } + if (operation == NodeAdd && recExists) || (operation == NodeDelete && !recExists) { + return nil + } + } + if operation == NodeAdd { + nodeList = append(nodeList, enodeId) + } else { + nodeList = append(nodeList[:index], nodeList[index+1:]...) + } + blob, _ := json.Marshal(nodeList) + + mux.Lock() + defer mux.Unlock() + + err := ioutil.WriteFile(fileName, blob, 0644) + return err +} + +//this function populates the black listed Node information into the disallowed-nodes.json file +func UpdateDisallowedNodes(dataDir, url string, operation NodeOperation) error { + + fileExists := true + path := filepath.Join(dataDir, params.BLACKLIST_CONFIG) + // Check if the file is existing. If the file is not existing create the file + if _, err := os.Stat(path); err != nil { + if _, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644); err != nil { + return err + } + fileExists = false + } + + if fileExists { + err := UpdateFile(path, url, operation, false) + return err + } else { + err := UpdateFile(path, url, operation, true) + return err + } +} + +// Disconnect the Node from the network +func DisconnectNode(node *node.Node, enodeId string, isRaft bool) error { + if isRaft { + var raftService *raft.RaftService + if err := node.Service(&raftService); err == nil { + raftApi := raft.NewPublicRaftAPI(raftService) + + //get the raftId for the given enodeId + raftId, err := raftApi.GetRaftId(enodeId) + if err == nil { + raftApi.RemovePeer(raftId) + } else { + return err + } + } + } else { + // Istanbul or clique - disconnect the peer + + server := node.Server() + if server != nil { + node, err := enode.ParseV4(enodeId) + if err == nil { + server.RemovePeer(node) + } else { + return err + } + } + } + return nil +} + +// updates Node information in the permissioned-nodes.json file based on Node +// management activities in smart contract +func UpdatePermissionedNodes(node *node.Node, dataDir, enodeId string, operation NodeOperation, isRaft bool) error { + path := filepath.Join(dataDir, params.PERMISSIONED_CONFIG) + if _, err := os.Stat(path); err != nil { + return err + } + + err := UpdateFile(path, enodeId, operation, false) + if err != nil { + return err + } + if operation == NodeDelete { + err := DisconnectNode(node, enodeId, isRaft) + if err != nil { + return err + } + } + return nil +} + +// function to subscribe to the stop event +func SubscribeStopEvent() (chan StopEvent, event.Subscription) { + c := make(chan StopEvent) + s := StopFeed.Subscribe(c) + return c, s +} + +// function reads the permissions config file passed and populates the +// config structure accordingly +func ParsePermissionConfig(dir string) (PermissionConfig, error) { + fullPath := filepath.Join(dir, params.PERMISSION_MODEL_CONFIG) + f, err := os.Open(fullPath) + if err != nil { + log.Error("can't open file", "file", fullPath, "error", err) + return PermissionConfig{}, err + } + defer func() { + _ = f.Close() + }() + + var permConfig PermissionConfig + blob, err := ioutil.ReadFile(fullPath) + if err != nil { + log.Error("error reading file", "err", err, "file", fullPath) + } + + err = json.Unmarshal(blob, &permConfig) + if err != nil { + log.Error("error unmarshalling the file", "err", err, "file", fullPath) + } + + if permConfig.PermissionsModel == "" { + return PermissionConfig{}, fmt.Errorf("permissions model type not passed in %s. Network cannot boot up", params.PERMISSION_MODEL_CONFIG) + } + + permConfig.PermissionsModel = strings.ToLower(permConfig.PermissionsModel) + if permConfig.PermissionsModel != PERMISSION_V2 && permConfig.PermissionsModel != PERMISSION_V1 { + return PermissionConfig{}, fmt.Errorf("invalid permissions model type passed in %s. Network cannot boot up", params.PERMISSION_MODEL_CONFIG) + } + + if len(permConfig.Accounts) == 0 { + return PermissionConfig{}, fmt.Errorf("no accounts given in %s. Network cannot boot up", params.PERMISSION_MODEL_CONFIG) + } + if permConfig.SubOrgDepth.Cmp(big.NewInt(0)) == 0 || permConfig.SubOrgBreadth.Cmp(big.NewInt(0)) == 0 { + return PermissionConfig{}, fmt.Errorf("sub org breadth depth not passed in %s. Network cannot boot up", params.PERMISSION_MODEL_CONFIG) + } + if permConfig.IsEmpty() { + return PermissionConfig{}, fmt.Errorf("missing contract addresses in %s", params.PERMISSION_MODEL_CONFIG) + } + + return permConfig, nil +} + +// returns the enode details +func GetNodeDetails(url string, isRaft, useDns bool) (string, string, uint16, uint16, error) { + // validate Node id and + var ip string + if len(url) == 0 { + return "", ip, 0, 0, errors.New("invalid Node id. empty url") + } + enodeDet, err := enode.ParseV4(url) + if err != nil { + return "", ip, 0, 0, fmt.Errorf("invalid Node id. %s", err.Error()) + } + + ip = enodeDet.IP().String() + if isRaft && useDns { + if enodeDet.Host() != "" { + ip = enodeDet.Host() + } + } + return enodeDet.EnodeID(), ip, uint16(enodeDet.TCP()), uint16(enodeDet.RaftPort()), err +} + +func (pc *PermissionConfig) IsEmpty() bool { + return pc.InterfAddress == common.HexToAddress("0x0") +} diff --git a/permission/core/types/contract.go b/permission/core/types/contract.go new file mode 100644 index 0000000000..975ec43632 --- /dev/null +++ b/permission/core/types/contract.go @@ -0,0 +1,130 @@ +package types + +import ( + "crypto/ecdsa" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/permission/core" +) + +// TxArgs holds arguments required for execute functions +type TxArgs struct { + OrgId string + POrgId string + Url string + RoleId string + IsVoter bool + IsAdmin bool + AcctId common.Address + AccessType uint8 + Action uint8 + Txa ethapi.SendTxArgs +} + +type ContractBackend struct { + EthClnt bind.ContractBackend + Key *ecdsa.PrivateKey + PermConfig *PermissionConfig + IsRaft bool + UseDns bool +} + +type RoleService interface { + AddNewRole(_args TxArgs) (*types.Transaction, error) + RemoveRole(_args TxArgs) (*types.Transaction, error) +} + +// Org services +type OrgService interface { + AddOrg(_args TxArgs) (*types.Transaction, error) + AddSubOrg(_args TxArgs) (*types.Transaction, error) + ApproveOrg(_args TxArgs) (*types.Transaction, error) + UpdateOrgStatus(_args TxArgs) (*types.Transaction, error) + ApproveOrgStatus(_args TxArgs) (*types.Transaction, error) +} + +// Node services +type NodeService interface { + AddNode(_args TxArgs) (*types.Transaction, error) + UpdateNodeStatus(_args TxArgs) (*types.Transaction, error) + StartBlacklistedNodeRecovery(_args TxArgs) (*types.Transaction, error) + ApproveBlacklistedNodeRecovery(_args TxArgs) (*types.Transaction, error) +} + +// Account services +type AccountService interface { + AssignAccountRole(_args TxArgs) (*types.Transaction, error) + AssignAdminRole(_args TxArgs) (*types.Transaction, error) + ApproveAdminRole(_args TxArgs) (*types.Transaction, error) + UpdateAccountStatus(_args TxArgs) (*types.Transaction, error) + StartBlacklistedAccountRecovery(_args TxArgs) (*types.Transaction, error) + ApproveBlacklistedAccountRecovery(_args TxArgs) (*types.Transaction, error) +} + +// Control services +type ControlService interface { + ConnectionAllowed(_enodeId, _ip string, _port, _raftPort uint16) (bool, error) + TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte, _transactionType core.TransactionType) error +} + +// Audit services +type AuditService interface { + ValidatePendingOp(authOrg, orgId, url string, account common.Address, pendingOp int64) bool + CheckPendingOp(_orgId string) bool +} + +type InitService interface { + BindContracts() error + Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) + UpdateNetworkBootStatus() (*types.Transaction, error) + SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) + GetNetworkBootStatus() (bool, error) + + AddAdminAccount(_acct common.Address) (*types.Transaction, error) + AddAdminNode(url string) (*types.Transaction, error) + GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) + GetNumberOfAccounts() (*big.Int, error) + GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) + + GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool + }, error) + GetNumberOfRoles() (*big.Int, error) + GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool + }, error) + + GetNumberOfOrgs() (*big.Int, error) + GetSubOrgIndexes(_orgId string) ([]*big.Int, error) + GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) + GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) + + GetNodeDetailsFromIndex(_nodeIndex *big.Int) (string, string, *big.Int, error) + GetNumberOfNodes() (*big.Int, error) + GetNodeDetails(enodeId string) (string, string, *big.Int, error) +} + +func BindContract(contractInstance interface{}, bindFunc func() (interface{}, error)) error { + element := reflect.ValueOf(contractInstance).Elem() + instance, err := bindFunc() + if err != nil { + return err + } + element.Set(reflect.ValueOf(instance)) + return nil +} diff --git a/permission/permission.go b/permission/permission.go new file mode 100644 index 0000000000..985e8991e0 --- /dev/null +++ b/permission/permission.go @@ -0,0 +1,416 @@ +package permission + +import ( + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + pcore "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" +) + +// This is to make sure all contract instances are ready and initialized +// +// Required to be call after standard service start lifecycle +func (p *PermissionCtrl) AfterStart() error { + log.Debug("permission service: binding contracts") + err := <-p.errorChan // capture any error happened during asyncStart. Also wait here if asyncStart is not yet finish + if err != nil { + return err + } + if err = p.contract.BindContracts(); err != nil { + return fmt.Errorf("populateInitPermissions failed to bind contracts: %v", err) + } + + // populate the initial list of permissioned nodes and account accesses + if err := p.populateInitPermissions(params.DEFAULT_ORGCACHE_SIZE, params.DEFAULT_ROLECACHE_SIZE, + params.DEFAULT_NODECACHE_SIZE, params.DEFAULT_ACCOUNTCACHE_SIZE); err != nil { + return fmt.Errorf("populateInitPermissions failed: %v", err) + } + + // set the function point for transaction allowed check + pcore.PermissionTransactionAllowedFunc = p.IsTransactionAllowed + setPermissionService(p) + + // set the default access to ReadOnly + pcore.SetDefaults(p.permConfig.NwAdminRole, p.permConfig.OrgAdminRole, p.IsV2Permission()) + for _, f := range []func() error{ + p.monitorQIP714Block, // monitor block number to activate new permissions controls + p.backend.ManageOrgPermissions, // monitor org management related events + p.backend.ManageNodePermissions, // monitor org level Node management events + p.backend.ManageRolePermissions, // monitor org level role management events + p.backend.ManageAccountPermissions, // monitor org level account management events + } { + if err := f(); err != nil { + return err + } + } + + log.Info("permission service: is now ready") + + return nil +} + +// start service asynchronously due to dependencies +func (p *PermissionCtrl) asyncStart() { + var ethereum *eth.Ethereum + // will be blocked here until Node is up + if err := p.node.Service(ðereum); err != nil { + p.errorChan <- fmt.Errorf("dependent ethereum service not started") + return + } + defer func() { + p.errorChan <- nil + }() + // for cases where the node is joining an existing network, permission service + // can be brought up only after block syncing is complete. This function + // waits for block syncing before the starting permissions + p.startWaitGroup.Add(1) + go func(_wg *sync.WaitGroup) { + log.Debug("permission service: waiting for downloader") + stopChan, stopSubscription := ptype.SubscribeStopEvent() + pollingTicker := time.NewTicker(10 * time.Millisecond) + defer func(start time.Time) { + log.Debug("permission service: downloader completed", "took", time.Since(start)) + stopSubscription.Unsubscribe() + pollingTicker.Stop() + _wg.Done() + }(time.Now()) + for { + select { + case <-pollingTicker.C: + if pcore.GetSyncStatus() && !ethereum.Downloader().Synchronising() { + return + } + case <-stopChan: + return + } + } + }(p.startWaitGroup) // wait for downloader to sync if any + + log.Debug("permission service: waiting for all dependencies to be ready") + p.startWaitGroup.Wait() + client, err := p.node.Attach() + if err != nil { + p.errorChan <- fmt.Errorf("unable to create rpc client: %v", err) + return + } + p.ethClnt = ethclient.NewClient(client) + p.eth = ethereum + p.isRaft = p.eth.BlockChain().Config().Istanbul == nil && p.eth.BlockChain().Config().Clique == nil + p.updateBackEnd() +} + +// monitors QIP714Block and set default access +func (p *PermissionCtrl) monitorQIP714Block() error { + // if QIP714block is not given, set the default access + // to readonly + if p.eth.BlockChain().Config().QIP714Block == nil || p.eth.BlockChain().Config().IsQIP714(p.eth.BlockChain().CurrentBlock().Number()) { + pcore.SetQIP714BlockReached() + return nil + } + //QIP714block is given, monitor block count + go func() { + chainHeadCh := make(chan core.ChainHeadEvent, 1) + headSub := p.eth.BlockChain().SubscribeChainHeadEvent(chainHeadCh) + defer headSub.Unsubscribe() + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case head := <-chainHeadCh: + if p.eth.BlockChain().Config().IsQIP714(head.Block.Number()) { + pcore.SetQIP714BlockReached() + return + } + case <-stopChan: + return + } + } + }() + return nil +} + +func (p *PermissionCtrl) instantiateCache(orgCacheSize, roleCacheSize, nodeCacheSize, accountCacheSize int) { + // instantiate the cache objects for permissions + pcore.OrgInfoMap = pcore.NewOrgCache(orgCacheSize) + pcore.OrgInfoMap.PopulateCacheFunc(p.populateOrgToCache) + + pcore.RoleInfoMap = pcore.NewRoleCache(roleCacheSize) + pcore.RoleInfoMap.PopulateCacheFunc(p.populateRoleToCache) + + pcore.NodeInfoMap = pcore.NewNodeCache(nodeCacheSize) + pcore.NodeInfoMap.PopulateCacheFunc(p.populateNodeCache) + pcore.NodeInfoMap.PopulateValidateFunc(p.populateNodeCacheAndValidate) + + pcore.AcctInfoMap = pcore.NewAcctCache(accountCacheSize) + pcore.AcctInfoMap.PopulateCacheFunc(p.populateAccountToCache) +} + +// Thus function checks if the initial network boot up status and if no +// populates permissions model with details from permission-config.json +func (p *PermissionCtrl) populateInitPermissions(orgCacheSize, roleCacheSize, nodeCacheSize, accountCacheSize int) error { + p.instantiateCache(orgCacheSize, roleCacheSize, nodeCacheSize, accountCacheSize) + networkInitialized, err := p.contract.GetNetworkBootStatus() + if err != nil { + // handle the scenario of no contract code. + log.Warn("Failed to retrieve network boot status ", "err", err) + return err + } + + if !networkInitialized { + p.backend.MonitorNetworkBootUp() + if err := p.bootupNetwork(); err != nil { + return err + } + } else { + //populate orgs, nodes, roles and accounts from contract + for _, f := range []func() error{ + p.populateOrgsFromContract, + p.populateNodesFromContract, + p.populateRolesFromContract, + p.populateAccountsFromContract, + } { + if err := f(); err != nil { + return err + } + } + pcore.SetNetworkBootUpCompleted() + } + return nil +} + +// initialize the permissions model and populate initial values +func (p *PermissionCtrl) bootupNetwork() error { + if _, err := p.contract.SetPolicy(p.permConfig.NwAdminOrg, p.permConfig.NwAdminRole, p.permConfig.OrgAdminRole); err != nil { + log.Error("bootupNetwork SetPolicy failed", "err", err) + return err + } + if _, err := p.contract.Init(p.permConfig.SubOrgBreadth, p.permConfig.SubOrgDepth); err != nil { + log.Error("bootupNetwork init failed", "err", err) + return err + } + + pcore.OrgInfoMap.UpsertOrg(p.permConfig.NwAdminOrg, "", p.permConfig.NwAdminOrg, big.NewInt(1), pcore.OrgApproved) + pcore.RoleInfoMap.UpsertRole(p.permConfig.NwAdminOrg, p.permConfig.NwAdminRole, true, true, pcore.FullAccess, true) + // populate the initial Node list from static-nodes.json + if err := p.populateStaticNodesToContract(); err != nil { + return err + } + // populate initial account access to full access + if err := p.populateInitAccountAccess(); err != nil { + return err + } + + // update network status to boot completed + if err := p.updateNetworkStatus(); err != nil { + log.Error("failed to updated network boot status", "error", err) + return err + } + return nil +} + +// populates the account access details from contract into cache +func (p *PermissionCtrl) populateAccountsFromContract() error { + if numberOfRoles, err := p.contract.GetNumberOfAccounts(); err == nil { + iOrgNum := numberOfRoles.Uint64() + for k := uint64(0); k < iOrgNum; k++ { + if addr, org, role, status, orgAdmin, err := p.contract.GetAccountDetailsFromIndex(big.NewInt(int64(k))); err == nil { + pcore.AcctInfoMap.UpsertAccount(org, role, addr, orgAdmin, pcore.AcctStatus(int(status.Int64()))) + } + } + } else { + return err + } + return nil +} + +// populates the role details from contract into cache +func (p *PermissionCtrl) populateRolesFromContract() error { + if numberOfRoles, err := p.contract.GetNumberOfRoles(); err == nil { + iOrgNum := numberOfRoles.Uint64() + for k := uint64(0); k < iOrgNum; k++ { + if roleStruct, err := p.contract.GetRoleDetailsFromIndex(big.NewInt(int64(k))); err == nil { + pcore.RoleInfoMap.UpsertRole(roleStruct.OrgId, roleStruct.RoleId, roleStruct.Voter, roleStruct.Admin, pcore.AccessType(int(roleStruct.AccessType.Int64())), roleStruct.Active) + } + } + + } else { + return err + } + return nil +} + +// populates the Node details from contract into cache +func (p *PermissionCtrl) populateNodesFromContract() error { + if numberOfNodes, err := p.contract.GetNumberOfNodes(); err == nil { + iOrgNum := numberOfNodes.Uint64() + for k := uint64(0); k < iOrgNum; k++ { + if orgId, url, status, err := p.contract.GetNodeDetailsFromIndex(big.NewInt(int64(k))); err == nil { + pcore.NodeInfoMap.UpsertNode(orgId, url, pcore.NodeStatus(int(status.Int64()))) + } + } + } else { + return err + } + return nil +} + +// populates the org details from contract into cache +func (p *PermissionCtrl) populateOrgsFromContract() error { + + if numberOfOrgs, err := p.contract.GetNumberOfOrgs(); err == nil { + iOrgNum := numberOfOrgs.Uint64() + for k := uint64(0); k < iOrgNum; k++ { + if orgId, porgId, ultParent, level, status, err := p.contract.GetOrgInfo(big.NewInt(int64(k))); err == nil { + pcore.OrgInfoMap.UpsertOrg(orgId, porgId, ultParent, level, pcore.OrgStatus(int(status.Int64()))) + } + } + } else { + return err + } + return nil +} + +// Reads the node list from static-nodes.json and populates into the contract +func (p *PermissionCtrl) populateStaticNodesToContract() error { + nodes := p.node.Server().Config.StaticNodes + for _, node := range nodes { + url := pcore.GetNodeUrl(node.EnodeID(), node.IP().String(), uint16(node.TCP()), uint16(node.RaftPort()), p.isRaft) + _, err := p.contract.AddAdminNode(url) + if err != nil { + log.Warn("Failed to propose node", "err", err, "enode", node.EnodeID()) + return err + } + pcore.NodeInfoMap.UpsertNode(p.permConfig.NwAdminOrg, url, 2) + } + return nil +} + +// Invokes the initAccounts function of smart contract to set the initial +// set of accounts access to full access +func (p *PermissionCtrl) populateInitAccountAccess() error { + for _, a := range p.permConfig.Accounts { + _, er := p.contract.AddAdminAccount(a) + if er != nil { + log.Warn("Error adding permission initial account list", "err", er, "account", a) + return er + } + pcore.AcctInfoMap.UpsertAccount(p.permConfig.NwAdminOrg, p.permConfig.NwAdminRole, a, true, 2) + } + return nil +} + +// updates network boot status to true +func (p *PermissionCtrl) updateNetworkStatus() error { + _, err := p.contract.UpdateNetworkBootStatus() + if err != nil { + log.Warn("Failed to udpate network boot status ", "err", err) + return err + } + return nil +} + +// getter to get an account record from the contract +func (p *PermissionCtrl) populateAccountToCache(acctId common.Address) (*pcore.AccountInfo, error) { + account, orgId, roleId, status, isAdmin, err := p.contract.GetAccountDetails(acctId) + if err != nil { + return nil, err + } + + if status.Int64() == 0 { + return nil, ptype.ErrAccountNotThere + } + return &pcore.AccountInfo{AcctId: account, OrgId: orgId, RoleId: roleId, Status: pcore.AcctStatus(status.Int64()), IsOrgAdmin: isAdmin}, nil +} + +// getter to get a org record from the contract +func (p *PermissionCtrl) populateOrgToCache(orgId string) (*pcore.OrgInfo, error) { + org, parentOrgId, ultimateParentId, orgLevel, orgStatus, err := p.contract.GetOrgDetails(orgId) + if err != nil { + return nil, err + } + if orgStatus.Int64() == 0 { + return nil, ptype.ErrOrgDoesNotExists + } + orgInfo := pcore.OrgInfo{OrgId: org, ParentOrgId: parentOrgId, UltimateParent: ultimateParentId, Status: pcore.OrgStatus(orgStatus.Int64()), Level: orgLevel} + // now need to build the list of sub orgs for this org + subOrgIndexes, err := p.contract.GetSubOrgIndexes(orgId) + if err != nil { + return nil, err + } + + if len(subOrgIndexes) == 0 { + return &orgInfo, nil + } + + // range through the sub org indexes and get the org ids to populate the suborg list + for _, s := range subOrgIndexes { + subOrgId, _, _, _, _, err := p.contract.GetOrgInfo(s) + + if err != nil { + return nil, err + } + orgInfo.SubOrgList = append(orgInfo.SubOrgList, orgId+"."+subOrgId) + + } + return &orgInfo, nil +} + +// getter to get a role record from the contract +func (p *PermissionCtrl) populateRoleToCache(roleKey *pcore.RoleKey) (*pcore.RoleInfo, error) { + roleDetails, err := p.contract.GetRoleDetails(roleKey.RoleId, roleKey.OrgId) + + if err != nil { + return nil, err + } + + if roleDetails.OrgId == "" { + return nil, ptype.ErrInvalidRole + } + return &pcore.RoleInfo{OrgId: roleDetails.OrgId, RoleId: roleDetails.RoleId, IsVoter: roleDetails.Voter, IsAdmin: roleDetails.Admin, Access: pcore.AccessType(roleDetails.AccessType.Int64()), Active: roleDetails.Active}, nil +} + +// getter to get a role record from the contract +func (p *PermissionCtrl) populateNodeCache(url string) (*pcore.NodeInfo, error) { + orgId, url, status, err := p.contract.GetNodeDetails(url) + if err != nil { + return nil, err + } + + if status.Int64() == 0 { + return nil, ptype.ErrNodeDoesNotExists + } + return &pcore.NodeInfo{OrgId: orgId, Url: url, Status: pcore.NodeStatus(status.Int64())}, nil +} + +// getter to get a Node record from the contract +func (p *PermissionCtrl) populateNodeCacheAndValidate(hexNodeId, ultimateParentId string) bool { + txnAllowed := false + passedEnode, _ := enode.ParseV4(hexNodeId) + if numberOfNodes, err := p.contract.GetNumberOfNodes(); err == nil { + numNodes := numberOfNodes.Uint64() + for k := uint64(0); k < numNodes; k++ { + if orgId, url, status, err := p.contract.GetNodeDetailsFromIndex(big.NewInt(int64(k))); err == nil { + if orgRec, err := pcore.OrgInfoMap.GetOrg(orgId); err != nil { + if orgRec.UltimateParent == ultimateParentId { + recEnode, _ := enode.ParseV4(url) + if recEnode.ID() == passedEnode.ID() { + txnAllowed = true + pcore.NodeInfoMap.UpsertNode(orgId, url, pcore.NodeStatus(int(status.Int64()))) + } + } + } + } + } + } + return txnAllowed +} diff --git a/permission/permission_test.go b/permission/permission_test.go new file mode 100644 index 0000000000..08e5aefb58 --- /dev/null +++ b/permission/permission_test.go @@ -0,0 +1,1002 @@ +package permission + +import ( + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "math/big" + "os" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/params" + pcore "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" + v1 "github.com/ethereum/go-ethereum/permission/v1" + v1bind "github.com/ethereum/go-ethereum/permission/v1/bind" + v2 "github.com/ethereum/go-ethereum/permission/v2" + v2bind "github.com/ethereum/go-ethereum/permission/v2/bind" + "github.com/stretchr/testify/assert" +) + +const ( + arbitraryNetworkAdminOrg = "NETWORK_ADMIN" + arbitraryNetworkAdminRole = "NETWORK_ADMIN_ROLE" + arbitraryOrgAdminRole = "ORG_ADMIN_ROLE" + arbitraryNode1 = "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@127.0.0.1:21000?discport=0&raftport=50401" + arbitraryNode2 = "enode://0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416@127.0.0.1:21001?discport=0&raftport=50402" + arbitraryNode3 = "enode://579f786d4e2830bbcc02815a27e8a9bacccc9605df4dc6f20bcc1a6eb391e7225fff7cb83e5b4ecd1f3a94d8b733803f2f66b7e871961e7b029e22c155c3a778@127.0.0.1:21002?discport=0&raftport=50403" + arbitraryNode4 = "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404" + arbitraryNode4withHostName = "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@lcoalhost:21003?discport=0&raftport=50404" + arbitraryOrgToAdd = "ORG1" + arbitrarySubOrg = "SUB1" + arbitrartNewRole1 = "NEW_ROLE_1" + arbitrartNewRole2 = "NEW_ROLE_2" + orgCacheSize = 4 + roleCacheSize = 4 + nodeCacheSize = 2 + accountCacheSize = 4 +) + +var ErrAccountsLinked = errors.New("Accounts linked to the role. Cannot be removed") +var ErrPendingApproval = errors.New("Pending approvals for the organization. Approve first") +var ErrAcctBlacklisted = errors.New("Blacklisted account. Operation not allowed") +var ErrNodeBlacklisted = errors.New("Blacklisted node. Operation not allowed") + +var ( + guardianKey *ecdsa.PrivateKey + guardianAccount accounts.Account + contrBackend bind.ContractBackend + ethereum *eth.Ethereum + stack *node.Node + guardianAddress common.Address + v2Flag bool + + permUpgrAddress, permInterfaceAddress, permImplAddress, voterManagerAddress, + nodeManagerAddress, roleManagerAddress, accountManagerAddress, orgManagerAddress common.Address +) + +func TestMain(m *testing.M) { + var v2FlagVer = []bool{false} + var ret int + for i := range v2FlagVer { + v2Flag = v2FlagVer[i] + setup() + ret = m.Run() + teardown() + if ret != 0 { + os.Exit(ret) + } + } + os.Exit(ret) +} + +func setup() { + var err error + t := log.New(os.Stdout, "", 0) + + ksdir, _, err := tmpKeyStore(false) + defer os.RemoveAll(ksdir) + + if err != nil { + t.Fatalf("failed to create keystore: %v\n", err) + } + nodeKey, _ := crypto.GenerateKey() + guardianKey, _ = crypto.GenerateKey() + + // Create a network-less protocol stack and start an Ethereum service within + stack, _ = node.New(&node.Config{ + DataDir: "", + KeyStoreDir: ksdir, + UseLightweightKDF: true, + P2P: p2p.Config{ + PrivateKey: nodeKey, + }, + }) + + ksbackend := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + guardianAccount, err = ksbackend.ImportECDSA(guardianKey, "foo") + guardianAddress = guardianAccount.Address + if err != nil { + t.Fatal(err) + } + err = ksbackend.TimedUnlock(guardianAccount, "foo", 0) + if err != nil { + t.Fatal("failed to unlock") + } + + genesisAlloc := map[common.Address]core.GenesisAccount{ + guardianAddress: { + Balance: big.NewInt(100000000000000), + }, + } + ethConf := ð.Config{ + Genesis: &core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: 10000000000, Alloc: genesisAlloc}, + Miner: miner.Config{Etherbase: guardianAddress}, + Ethash: ethash.Config{ + PowMode: ethash.ModeTest, + }, + } + + if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + t.Fatalf("failed to register Ethereum protocol: %v", err) + } + // Start the Node and assemble the JavaScript console around it + if err = stack.Start(); err != nil { + t.Fatalf("failed to start test stack: %v", err) + } + if err := stack.Service(ðereum); err != nil { + t.Fatal(err) + } + contrBackend = backends.NewSimulatedBackendFrom(ethereum) + + var permUpgrInstance *v1bind.PermUpgr + var permUpgrInstanceE *v2bind.PermUpgr + + guardianTransactor := bind.NewKeyedTransactor(guardianKey) + + if v2Flag { + permUpgrAddress, _, permUpgrInstanceE, err = v2bind.DeployPermUpgr(guardianTransactor, contrBackend, guardianAddress) + if err != nil { + t.Fatal(err) + } + permInterfaceAddress, _, _, err = v2bind.DeployPermInterface(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + nodeManagerAddress, _, _, err = v2bind.DeployNodeManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + roleManagerAddress, _, _, err = v2bind.DeployRoleManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + accountManagerAddress, _, _, err = v2bind.DeployAcctManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + orgManagerAddress, _, _, err = v2bind.DeployOrgManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + voterManagerAddress, _, _, err = v2bind.DeployVoterManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + permImplAddress, _, _, err = v2bind.DeployPermImpl(guardianTransactor, contrBackend, permUpgrAddress, orgManagerAddress, roleManagerAddress, accountManagerAddress, voterManagerAddress, nodeManagerAddress) + if err != nil { + t.Fatal(err) + } + // call init + if _, err := permUpgrInstanceE.Init(guardianTransactor, permInterfaceAddress, permImplAddress); err != nil { + t.Fatal(err) + } + } else { + permUpgrAddress, _, permUpgrInstance, err = v1bind.DeployPermUpgr(guardianTransactor, contrBackend, guardianAddress) + if err != nil { + t.Fatal(err) + } + permInterfaceAddress, _, _, err = v1bind.DeployPermInterface(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + nodeManagerAddress, _, _, err = v1bind.DeployNodeManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + roleManagerAddress, _, _, err = v1bind.DeployRoleManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + accountManagerAddress, _, _, err = v1bind.DeployAcctManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + orgManagerAddress, _, _, err = v1bind.DeployOrgManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + voterManagerAddress, _, _, err = v1bind.DeployVoterManager(guardianTransactor, contrBackend, permUpgrAddress) + if err != nil { + t.Fatal(err) + } + permImplAddress, _, _, err = v1bind.DeployPermImpl(guardianTransactor, contrBackend, permUpgrAddress, orgManagerAddress, roleManagerAddress, accountManagerAddress, voterManagerAddress, nodeManagerAddress) + if err != nil { + t.Fatal(err) + } + // call init + if _, err := permUpgrInstance.Init(guardianTransactor, permInterfaceAddress, permImplAddress); err != nil { + t.Fatal(err) + } + } + fmt.Printf("current block is %v\n", ethereum.BlockChain().CurrentBlock().Number().Int64()) +} + +func teardown() { + +} + +func TestPermissionCtrl_AfterStart(t *testing.T) { + testObject := typicalPermissionCtrl(t, v2Flag) + + err := testObject.AfterStart() + + assert.NoError(t, err) + if testObject.IsV2Permission() { + var contract *v2.Init + contract, _ = testObject.contract.(*v2.Init) + assert.NotNil(t, contract.PermOrg) + assert.NotNil(t, contract.PermRole) + assert.NotNil(t, contract.PermNode) + assert.NotNil(t, contract.PermAcct) + assert.NotNil(t, contract.PermInterf) + assert.NotNil(t, contract.PermUpgr) + } else { + var contract *v1.Init + contract, _ = testObject.contract.(*v1.Init) + assert.NotNil(t, contract.PermOrg) + assert.NotNil(t, contract.PermRole) + assert.NotNil(t, contract.PermNode) + assert.NotNil(t, contract.PermAcct) + assert.NotNil(t, contract.PermInterf) + assert.NotNil(t, contract.PermUpgr) + } + + isNetworkInitialized, err := testObject.contract.GetNetworkBootStatus() + assert.NoError(t, err) + assert.True(t, isNetworkInitialized) +} + +func TestPermissionCtrl_PopulateInitPermissions_AfterNetworkIsInitialized(t *testing.T) { + testObject := typicalPermissionCtrl(t, v2Flag) + assert.NoError(t, testObject.AfterStart()) + + err := testObject.populateInitPermissions(orgCacheSize, roleCacheSize, nodeCacheSize, accountCacheSize) + + assert.NoError(t, err) + + // assert cache + assert.Equal(t, 1, len(pcore.OrgInfoMap.GetOrgList())) + cachedOrg := pcore.OrgInfoMap.GetOrgList()[0] + assert.Equal(t, arbitraryNetworkAdminOrg, cachedOrg.OrgId) + assert.Equal(t, arbitraryNetworkAdminOrg, cachedOrg.FullOrgId) + assert.Equal(t, arbitraryNetworkAdminOrg, cachedOrg.UltimateParent) + assert.Equal(t, "", cachedOrg.ParentOrgId) + assert.Equal(t, pcore.OrgApproved, cachedOrg.Status) + assert.Equal(t, 0, len(cachedOrg.SubOrgList)) + assert.Equal(t, big.NewInt(1), cachedOrg.Level) + + assert.Equal(t, 1, len(pcore.RoleInfoMap.GetRoleList())) + cachedRole := pcore.RoleInfoMap.GetRoleList()[0] + assert.Equal(t, arbitraryNetworkAdminOrg, cachedRole.OrgId) + assert.Equal(t, arbitraryNetworkAdminRole, cachedRole.RoleId) + assert.True(t, cachedRole.Active) + assert.True(t, cachedRole.IsAdmin) + assert.True(t, cachedRole.IsVoter) + assert.Equal(t, pcore.FullAccess, cachedRole.Access) + + assert.Equal(t, 0, len(pcore.NodeInfoMap.GetNodeList())) + + assert.Equal(t, 1, len(pcore.AcctInfoMap.GetAcctList())) + cachedAccount := pcore.AcctInfoMap.GetAcctList()[0] + assert.Equal(t, arbitraryNetworkAdminOrg, cachedAccount.OrgId) + assert.Equal(t, arbitraryNetworkAdminRole, cachedAccount.RoleId) + assert.Equal(t, pcore.AcctActive, cachedAccount.Status) + assert.True(t, cachedAccount.IsOrgAdmin) + assert.Equal(t, guardianAddress, cachedAccount.AcctId) +} + +func typicalQuorumControlsAPI(t *testing.T) *QuorumControlsAPI { + pc := typicalPermissionCtrl(t, v2Flag) + if !assert.NoError(t, pc.AfterStart()) { + t.Fail() + } + if !assert.NoError(t, pc.populateInitPermissions(orgCacheSize, roleCacheSize, nodeCacheSize, accountCacheSize)) { + t.Fail() + } + return NewQuorumControlsAPI(pc) +} + +func TestQuorumControlsAPI_ListAPIs(t *testing.T) { + testObject := typicalQuorumControlsAPI(t) + + orgDetails, err := testObject.GetOrgDetails(arbitraryNetworkAdminOrg) + assert.NoError(t, err) + assert.Equal(t, orgDetails.AcctList[0].AcctId, guardianAddress) + assert.Equal(t, orgDetails.RoleList[0].RoleId, arbitraryNetworkAdminRole) + + orgDetails, err = testObject.GetOrgDetails("XYZ") + assert.Equal(t, err, errors.New("Org does not exist")) + + // test NodeList + assert.Equal(t, len(testObject.NodeList()), 0) + // test AcctList + assert.True(t, len(testObject.AcctList()) > 0, "expected non zero account list") + // test OrgList + assert.True(t, len(testObject.OrgList()) > 0, "expected non zero org list") + // test RoleList + assert.True(t, len(testObject.RoleList()) > 0, "expected non zero org list") +} + +func TestQuorumControlsAPI_OrgAPIs(t *testing.T) { + testObject := typicalQuorumControlsAPI(t) + invalidTxa := ethapi.SendTxArgs{From: getArbitraryAccount()} + + // test AddOrg + orgAdminKey, _ := crypto.GenerateKey() + orgAdminAddress := crypto.PubkeyToAddress(orgAdminKey.PublicKey) + + txa := ethapi.SendTxArgs{From: guardianAddress} + _, err := testObject.AddOrg(arbitraryOrgToAdd, arbitraryNode1, orgAdminAddress, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.AddOrg(arbitraryOrgToAdd, arbitraryNode1, orgAdminAddress, txa) + assert.NoError(t, err) + + _, err = testObject.AddOrg(arbitraryOrgToAdd, arbitraryNode1, orgAdminAddress, txa) + assert.Equal(t, err, ErrPendingApproval) + + _, err = testObject.ApproveOrg(arbitraryOrgToAdd, arbitraryNode1, orgAdminAddress, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.ApproveOrg("XYZ", arbitraryNode1, orgAdminAddress, txa) + assert.Equal(t, err, errors.New("Nothing to approve")) + + _, err = testObject.ApproveOrg(arbitraryOrgToAdd, arbitraryNode1, orgAdminAddress, txa) + assert.NoError(t, err) + + pcore.OrgInfoMap.UpsertOrg(arbitraryOrgToAdd, "", arbitraryOrgToAdd, big.NewInt(1), pcore.OrgApproved) + _, err = testObject.UpdateOrgStatus(arbitraryOrgToAdd, uint8(SuspendOrg), invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.UpdateOrgStatus(arbitraryOrgToAdd, uint8(SuspendOrg), txa) + assert.NoError(t, err) + + pcore.OrgInfoMap.UpsertOrg(arbitraryOrgToAdd, "", arbitraryOrgToAdd, big.NewInt(1), pcore.OrgSuspended) + _, err = testObject.ApproveOrgStatus(arbitraryOrgToAdd, uint8(SuspendOrg), invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.ApproveOrgStatus(arbitraryOrgToAdd, uint8(SuspendOrg), txa) + assert.NoError(t, err) + + _, err = testObject.AddSubOrg(arbitraryNetworkAdminOrg, arbitrarySubOrg, "", invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.AddSubOrg(arbitraryNetworkAdminOrg, arbitrarySubOrg, "", txa) + assert.NoError(t, err) + pcore.OrgInfoMap.UpsertOrg(arbitrarySubOrg, arbitraryNetworkAdminOrg, arbitraryNetworkAdminOrg, big.NewInt(2), pcore.OrgApproved) + + suborg := "ABC.12345" + _, err = testObject.AddSubOrg(arbitraryNetworkAdminOrg, suborg, "", txa) + assert.Equal(t, err, errors.New("Org id cannot contain special characters")) + + _, err = testObject.AddSubOrg(arbitraryNetworkAdminOrg, "", "", txa) + assert.Equal(t, err, errors.New("Invalid input")) + + // caching tests - cache size for org is 4. add 4 sub orgs + // this will result in cache eviction + // get org details after this + for i := 0; i < orgCacheSize; i++ { + subOrgId := "TESTSUBORG" + strconv.Itoa(i) + _, err = testObject.AddSubOrg(arbitraryNetworkAdminOrg, subOrgId, "", txa) + assert.NoError(t, err) + pcore.OrgInfoMap.UpsertOrg(subOrgId, arbitraryNetworkAdminOrg, arbitraryNetworkAdminOrg, big.NewInt(2), pcore.OrgApproved) + } + + assert.Equal(t, orgCacheSize, len(pcore.OrgInfoMap.GetOrgList())) + + orgDetails, _ := testObject.GetOrgDetails(arbitraryNetworkAdminOrg) + assert.Equal(t, orgDetails.AcctList[0].AcctId, guardianAddress) + assert.Equal(t, orgDetails.RoleList[0].RoleId, arbitraryNetworkAdminRole) +} + +func testConnectionAllowed(t *testing.T, q *QuorumControlsAPI, url string, expected bool) { + enode, ip, port, raftPort, err := ptype.GetNodeDetails(url, false, false) + if q.permCtrl.IsV2Permission() { + assert.NoError(t, err) + connAllowed := q.ConnectionAllowed(enode, ip, port, raftPort) + assert.Equal(t, expected, connAllowed) + } else { + assert.Equal(t, isNodePermissionedV1(url, enode, enode, "INCOMING"), expected) + } +} + +func TestQuorumControlsAPI_NodeAPIs(t *testing.T) { + testObject := typicalQuorumControlsAPI(t) + invalidTxa := ethapi.SendTxArgs{From: getArbitraryAccount()} + txa := ethapi.SendTxArgs{From: guardianAddress} + + testObject.permCtrl.isRaft = true + _, err := testObject.AddNode(arbitraryNetworkAdminOrg, arbitraryNode2, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + testConnectionAllowed(t, testObject, arbitraryNode2, false) + + _, err = testObject.AddNode(arbitraryNetworkAdminOrg, arbitraryNode2, txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode2, pcore.NodeApproved) + testConnectionAllowed(t, testObject, arbitraryNode2, true) + + _, err = testObject.UpdateNodeStatus(arbitraryNetworkAdminOrg, arbitraryNode2, uint8(SuspendNode), invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.UpdateNodeStatus(arbitraryNetworkAdminOrg, arbitraryNode2, uint8(SuspendNode), txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode2, pcore.NodeDeactivated) + testConnectionAllowed(t, testObject, arbitraryNode2, false) + + _, err = testObject.UpdateNodeStatus(arbitraryNetworkAdminOrg, arbitraryNode2, uint8(ActivateSuspendedNode), txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode2, pcore.NodeApproved) + testConnectionAllowed(t, testObject, arbitraryNode2, true) + + _, err = testObject.UpdateNodeStatus(arbitraryNetworkAdminOrg, arbitraryNode2, uint8(BlacklistNode), txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode2, pcore.NodeBlackListed) + + _, err = testObject.UpdateNodeStatus(arbitraryNetworkAdminOrg, arbitraryNode2, uint8(ActivateSuspendedNode), txa) + assert.Equal(t, err, ErrNodeBlacklisted) + + _, err = testObject.RecoverBlackListedNode(arbitraryNetworkAdminOrg, arbitraryNode2, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.RecoverBlackListedNode(arbitraryNetworkAdminOrg, arbitraryNode2, txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode2, pcore.NodeRecoveryInitiated) + + _, err = testObject.ApproveBlackListedNodeRecovery(arbitraryNetworkAdminOrg, arbitraryNode2, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.ApproveBlackListedNodeRecovery(arbitraryNetworkAdminOrg, arbitraryNode2, txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode2, pcore.NodeApproved) + + // caching tests - cache size for Node is 3. add 2 nodes which will + // result in Node eviction from cache. get evicted Node details using api + _, err = testObject.AddNode(arbitraryNetworkAdminOrg, arbitraryNode3, txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode3, pcore.NodeApproved) + + testObject.permCtrl.isRaft = true + _, err = testObject.AddNode(arbitraryNetworkAdminOrg, arbitraryNode4withHostName, txa) + assert.Equal(t, err, ptype.ErrHostNameNotSupported) + + _, err = testObject.AddNode(arbitraryNetworkAdminOrg, arbitraryNode4, txa) + assert.NoError(t, err) + pcore.NodeInfoMap.UpsertNode(arbitraryNetworkAdminOrg, arbitraryNode4, pcore.NodeApproved) + + assert.Equal(t, nodeCacheSize, len(pcore.NodeInfoMap.GetNodeList())) + nodeInfo, err := pcore.NodeInfoMap.GetNodeByUrl(arbitraryNode4) + assert.True(t, err == nil, "Node fetch returned error") + assert.Equal(t, pcore.NodeApproved, nodeInfo.Status) +} + +func testTransactionAllowed(t *testing.T, q *QuorumControlsAPI, txa ethapi.SendTxArgs, expected bool) { + actAllowed := q.TransactionAllowed(txa) + assert.Equal(t, expected, actAllowed) +} + +func TestQuorumControlsAPI_TransactionAllowed(t *testing.T) { + testObject := typicalQuorumControlsAPI(t) + + if testObject.permCtrl.IsV2Permission() { + + acct := getArbitraryAccount() + txa := ethapi.SendTxArgs{From: guardianAddress} + payload := hexutil.Bytes(([]byte("0x43d3e767000000000000000000000000000000000000000000000000000000000000000a"))[:]) + value := hexutil.Big(*(big.NewInt(10))) + + transactionTxa := ethapi.SendTxArgs{From: acct, To: &guardianAddress, Value: &value} + contractCallTxa := ethapi.SendTxArgs{From: acct, To: &guardianAddress, Data: &payload} + contractCreateTxa := ethapi.SendTxArgs{From: acct, To: &common.Address{}, Data: &payload} + + for i := 0; i < 8; i++ { + roleId := arbitrartNewRole1 + strconv.Itoa(i) + _, err := testObject.AddNewRole(arbitraryNetworkAdminOrg, roleId, uint8(i), false, false, txa) + assert.NoError(t, err) + pcore.RoleInfoMap.UpsertRole(arbitraryNetworkAdminOrg, roleId, false, false, pcore.AccessType(uint8(i)), true) + + if i == 0 { + _, err = testObject.AddAccountToOrg(acct, arbitraryNetworkAdminOrg, roleId, txa) + assert.NoError(t, err) + } else { + _, err = testObject.ChangeAccountRole(acct, arbitraryNetworkAdminOrg, roleId, txa) + assert.NoError(t, err) + } + + switch pcore.AccessType(uint8(i)) { + case pcore.ReadOnly: + testTransactionAllowed(t, testObject, transactionTxa, false) + testTransactionAllowed(t, testObject, contractCallTxa, false) + testTransactionAllowed(t, testObject, contractCreateTxa, false) + + case pcore.Transact: + testTransactionAllowed(t, testObject, transactionTxa, true) + testTransactionAllowed(t, testObject, contractCallTxa, false) + testTransactionAllowed(t, testObject, contractCreateTxa, false) + + case pcore.ContractDeploy: + testTransactionAllowed(t, testObject, transactionTxa, false) + testTransactionAllowed(t, testObject, contractCallTxa, false) + testTransactionAllowed(t, testObject, contractCreateTxa, true) + + case pcore.FullAccess: + testTransactionAllowed(t, testObject, transactionTxa, true) + testTransactionAllowed(t, testObject, contractCallTxa, true) + testTransactionAllowed(t, testObject, contractCreateTxa, true) + + case pcore.ContractCall: + testTransactionAllowed(t, testObject, transactionTxa, false) + testTransactionAllowed(t, testObject, contractCallTxa, true) + testTransactionAllowed(t, testObject, contractCreateTxa, false) + + case pcore.TransactAndContractCall: + testTransactionAllowed(t, testObject, transactionTxa, true) + testTransactionAllowed(t, testObject, contractCallTxa, true) + testTransactionAllowed(t, testObject, contractCreateTxa, false) + + case pcore.TransactAndContractDeploy: + testTransactionAllowed(t, testObject, transactionTxa, true) + testTransactionAllowed(t, testObject, contractCallTxa, false) + testTransactionAllowed(t, testObject, contractCreateTxa, true) + case pcore.ContractCallAndDeploy: + testTransactionAllowed(t, testObject, transactionTxa, false) + testTransactionAllowed(t, testObject, contractCallTxa, true) + testTransactionAllowed(t, testObject, contractCreateTxa, true) + + } + + } + } + +} + +func TestQuorumControlsAPI_RoleAndAccountsAPIs(t *testing.T) { + testObject := typicalQuorumControlsAPI(t) + invalidTxa := ethapi.SendTxArgs{From: getArbitraryAccount()} + acct := getArbitraryAccount() + txa := ethapi.SendTxArgs{From: guardianAddress, To: &acct} + + pcore.SetNetworkBootUpCompleted() + pcore.SetQIP714BlockReached() + + _, err := testObject.AssignAdminRole(arbitraryNetworkAdminOrg, acct, arbitraryNetworkAdminRole, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, _ = testObject.AssignAdminRole(arbitraryNetworkAdminOrg, acct, arbitraryNetworkAdminRole, txa) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitraryNetworkAdminRole, acct, true, pcore.AcctPendingApproval) + + _, err = testObject.ApproveAdminRole(arbitraryNetworkAdminOrg, acct, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + testTransactionAllowed(t, testObject, ethapi.SendTxArgs{From: acct, To: &acct}, false) + + _, err = testObject.ApproveAdminRole(arbitraryNetworkAdminOrg, acct, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.ApproveAdminRole(arbitraryNetworkAdminOrg, acct, txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitraryNetworkAdminRole, acct, true, pcore.AcctActive) + testTransactionAllowed(t, testObject, ethapi.SendTxArgs{From: acct, To: &acct}, true) + + _, err = testObject.AddNewRole(arbitraryNetworkAdminOrg, arbitrartNewRole1, uint8(pcore.FullAccess), false, false, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.AddNewRole(arbitraryNetworkAdminOrg, arbitrartNewRole1, uint8(pcore.FullAccess), false, false, txa) + assert.NoError(t, err) + pcore.RoleInfoMap.UpsertRole(arbitraryNetworkAdminOrg, arbitrartNewRole1, false, false, pcore.FullAccess, true) + + acct = getArbitraryAccount() + _, err = testObject.AddAccountToOrg(acct, arbitraryNetworkAdminOrg, arbitrartNewRole1, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.AddAccountToOrg(acct, arbitraryNetworkAdminOrg, arbitrartNewRole1, txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole1, acct, true, pcore.AcctActive) + + _, err = testObject.RemoveRole(arbitraryNetworkAdminOrg, arbitrartNewRole1, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.RemoveRole(arbitraryNetworkAdminOrg, arbitrartNewRole1, txa) + assert.Equal(t, err, ErrAccountsLinked) + + _, err = testObject.AddNewRole(arbitraryNetworkAdminOrg, arbitrartNewRole2, uint8(pcore.FullAccess), false, false, txa) + assert.NoError(t, err) + pcore.RoleInfoMap.UpsertRole(arbitraryNetworkAdminOrg, arbitrartNewRole2, false, false, pcore.FullAccess, true) + + _, err = testObject.ChangeAccountRole(acct, arbitraryNetworkAdminOrg, arbitrartNewRole2, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.ChangeAccountRole(acct, arbitraryNetworkAdminOrg, arbitrartNewRole2, txa) + assert.NoError(t, err) + + _, err = testObject.RemoveRole(arbitraryNetworkAdminOrg, arbitrartNewRole1, txa) + assert.Equal(t, err, ErrAccountsLinked) + + _, err = testObject.UpdateAccountStatus(arbitraryNetworkAdminOrg, acct, uint8(SuspendAccount), invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.UpdateAccountStatus(arbitraryNetworkAdminOrg, acct, uint8(SuspendAccount), txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole2, acct, true, pcore.AcctSuspended) + testTransactionAllowed(t, testObject, ethapi.SendTxArgs{From: acct, To: &acct}, false) + + _, err = testObject.UpdateAccountStatus(arbitraryNetworkAdminOrg, acct, uint8(ActivateSuspendedAccount), txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole2, acct, true, pcore.AcctActive) + + _, err = testObject.UpdateAccountStatus(arbitraryNetworkAdminOrg, acct, uint8(BlacklistAccount), txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole2, acct, true, pcore.AcctBlacklisted) + testTransactionAllowed(t, testObject, ethapi.SendTxArgs{From: acct, To: &acct}, false) + + _, err = testObject.UpdateAccountStatus(arbitraryNetworkAdminOrg, acct, uint8(ActivateSuspendedAccount), txa) + assert.Equal(t, err, ErrAcctBlacklisted) + + _, err = testObject.RecoverBlackListedAccount(arbitraryNetworkAdminOrg, acct, invalidTxa) + assert.Equal(t, err, errors.New("Invalid account id")) + + _, err = testObject.RecoverBlackListedAccount(arbitraryNetworkAdminOrg, acct, txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole2, acct, true, pcore.AcctRecoveryInitiated) + _, err = testObject.ApproveBlackListedAccountRecovery(arbitraryNetworkAdminOrg, acct, txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole2, acct, true, pcore.AcctActive) + + // check role cache. the cache size is set to 4 + // insert 4 records and then retrieve the 1st role + for i := 0; i < roleCacheSize; i++ { + roleId := "TESTROLE" + strconv.Itoa(i) + _, err = testObject.AddNewRole(arbitraryNetworkAdminOrg, roleId, uint8(pcore.FullAccess), false, false, txa) + assert.NoError(t, err) + pcore.RoleInfoMap.UpsertRole(arbitraryNetworkAdminOrg, roleId, false, false, pcore.FullAccess, true) + } + + assert.Equal(t, roleCacheSize, len(pcore.RoleInfoMap.GetRoleList())) + roleInfo, err := pcore.RoleInfoMap.GetRole(arbitraryNetworkAdminOrg, arbitrartNewRole1) + assert.True(t, err == nil, "error encountered") + + assert.Equal(t, roleInfo.RoleId, arbitrartNewRole1) + + // check account cache + var AccountArray [4]common.Address + AccountArray[0] = common.StringToAddress("0fbdc686b912d7722dc86510934589e0aaf3b55a") + AccountArray[1] = common.StringToAddress("9186eb3d20cbd1f5f992a950d808c4495153abd5") + AccountArray[2] = common.StringToAddress("0638e1574728b6d862dd5d3a3e0942c3be47d996") + AccountArray[3] = common.StringToAddress("ae9bc6cd5145e67fbd1887a5145271fd182f0ee7") + + for i := 0; i < accountCacheSize; i++ { + _, err = testObject.AddAccountToOrg(AccountArray[i], arbitraryNetworkAdminOrg, arbitrartNewRole1, txa) + assert.NoError(t, err) + pcore.AcctInfoMap.UpsertAccount(arbitraryNetworkAdminOrg, arbitrartNewRole1, AccountArray[i], false, pcore.AcctActive) + } + assert.Equal(t, accountCacheSize, len(pcore.AcctInfoMap.GetAcctList())) + + acctInfo, err := pcore.AcctInfoMap.GetAccount(acct) + assert.True(t, err == nil, "error encountered") + assert.True(t, acctInfo != nil, "account details nil") +} + +func getArbitraryAccount() common.Address { + acctKey, _ := crypto.GenerateKey() + return crypto.PubkeyToAddress(acctKey.PublicKey) +} + +func typicalPermissionCtrl(t *testing.T, v2Flag bool) *PermissionCtrl { + pconfig := &ptype.PermissionConfig{ + UpgrdAddress: permUpgrAddress, + InterfAddress: permInterfaceAddress, + ImplAddress: permImplAddress, + NodeAddress: nodeManagerAddress, + AccountAddress: accountManagerAddress, + RoleAddress: roleManagerAddress, + VoterAddress: voterManagerAddress, + OrgAddress: orgManagerAddress, + NwAdminOrg: arbitraryNetworkAdminOrg, + NwAdminRole: arbitraryNetworkAdminRole, + OrgAdminRole: arbitraryOrgAdminRole, + Accounts: []common.Address{ + guardianAddress, + }, + SubOrgDepth: big.NewInt(10), + SubOrgBreadth: big.NewInt(10), + } + if v2Flag { + pconfig.PermissionsModel = ptype.PERMISSION_V2 + } else { + pconfig.PermissionsModel = ptype.PERMISSION_V1 + } + testObject, err := NewQuorumPermissionCtrl(stack, pconfig, false) + if err != nil { + t.Fatal(err) + } + + testObject.ethClnt = contrBackend + testObject.eth = ethereum + + // set contract and backend's contract as asyncStart won't get called + testObject.contract = NewPermissionContractService(testObject.ethClnt, testObject.IsV2Permission(), testObject.key, testObject.permConfig, false, false) + if v2Flag { + b := testObject.backend.(*v2.Backend) + b.Contr = testObject.contract.(*v2.Init) + } else { + b := testObject.backend.(*v1.Backend) + b.Contr = testObject.contract.(*v1.Init) + } + + go func() { + testObject.errorChan <- nil + }() + return testObject +} + +func tmpKeyStore(encrypted bool) (string, *keystore.KeyStore, error) { + d, err := ioutil.TempDir("", "Eth-keystore-test") + if err != nil { + return "", nil, err + } + newKs := keystore.NewPlaintextKeyStore + if encrypted { + newKs = func(kd string) *keystore.KeyStore { + return keystore.NewKeyStore(kd, keystore.LightScryptN, keystore.LightScryptP) + } + } + return d, newKs(d), err +} + +func TestPermissionCtrl_whenUpdateFile(t *testing.T) { + testObject := typicalPermissionCtrl(t, v2Flag) + assert.NoError(t, testObject.AfterStart()) + + err := testObject.populateInitPermissions(orgCacheSize, roleCacheSize, nodeCacheSize, accountCacheSize) + assert.NoError(t, err) + + d, _ := ioutil.TempDir("", "qdata") + defer os.RemoveAll(d) + + testObject.dataDir = d + ptype.UpdatePermissionedNodes(testObject.node, d, arbitraryNode1, ptype.NodeAdd, true) + + permFile, _ := os.Create(d + "/" + "permissioned-nodes.json") + + ptype.UpdateFile("testFile", arbitraryNode2, ptype.NodeAdd, false) + ptype.UpdateFile(permFile.Name(), arbitraryNode2, ptype.NodeAdd, false) + ptype.UpdateFile(permFile.Name(), arbitraryNode2, ptype.NodeAdd, true) + ptype.UpdateFile(permFile.Name(), arbitraryNode2, ptype.NodeAdd, true) + ptype.UpdateFile(permFile.Name(), arbitraryNode1, ptype.NodeAdd, false) + ptype.UpdateFile(permFile.Name(), arbitraryNode1, ptype.NodeDelete, false) + ptype.UpdateFile(permFile.Name(), arbitraryNode1, ptype.NodeDelete, false) + + blob, _ := ioutil.ReadFile(permFile.Name()) + var nodeList []string + if err := json.Unmarshal(blob, &nodeList); err != nil { + t.Fatal("Failed to load nodes list from file", "fileName", permFile, "err", err) + return + } + assert.Equal(t, len(nodeList), 1) + ptype.UpdatePermissionedNodes(testObject.node, d, arbitraryNode1, ptype.NodeAdd, true) + ptype.UpdatePermissionedNodes(testObject.node, d, arbitraryNode1, ptype.NodeDelete, true) + + blob, _ = ioutil.ReadFile(permFile.Name()) + if err := json.Unmarshal(blob, &nodeList); err != nil { + t.Fatal("Failed to load nodes list from file", "fileName", permFile, "err", err) + return + } + assert.Equal(t, len(nodeList), 1) + + ptype.UpdateDisallowedNodes(d, arbitraryNode2, ptype.NodeAdd) + ptype.UpdateDisallowedNodes(d, arbitraryNode2, ptype.NodeDelete) + blob, _ = ioutil.ReadFile(d + "/" + "disallowed-nodes.json") + if err := json.Unmarshal(blob, &nodeList); err != nil { + t.Fatal("Failed to load nodes list from file", "fileName", permFile, "err", err) + return + } + assert.Equal(t, len(nodeList), 0) + +} + +func TestParsePermissionConfig(t *testing.T) { + d, _ := ioutil.TempDir("", "qdata") + defer os.RemoveAll(d) + + _, err := ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "expected file not there error") + + fileName := d + "/permission-config.json" + os.Create(fileName) + _, err = ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "expected unmarshalling error") + + // write permission-config.json into the temp dir + var tmpPermCofig ptype.PermissionConfig + tmpPermCofig.NwAdminOrg = arbitraryNetworkAdminOrg + tmpPermCofig.NwAdminRole = arbitraryNetworkAdminRole + tmpPermCofig.OrgAdminRole = arbitraryOrgAdminRole + tmpPermCofig.InterfAddress = common.Address{} + tmpPermCofig.ImplAddress = common.Address{} + tmpPermCofig.UpgrdAddress = common.Address{} + tmpPermCofig.VoterAddress = common.Address{} + tmpPermCofig.RoleAddress = common.Address{} + tmpPermCofig.OrgAddress = common.Address{} + tmpPermCofig.NodeAddress = common.Address{} + tmpPermCofig.SubOrgBreadth = new(big.Int) + tmpPermCofig.SubOrgDepth = new(big.Int) + + blob, _ := json.Marshal(tmpPermCofig) + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + _, err = ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "permission model not given error") + + _ = os.Remove(fileName) + tmpPermCofig.PermissionsModel = "ABCD" + blob, _ = json.Marshal(tmpPermCofig) + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + + _, err = ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "invalid permission model error") + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + + _ = os.Remove(fileName) + tmpPermCofig.PermissionsModel = "v1" + blob, _ = json.Marshal(tmpPermCofig) + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + + _, err = ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "expected account not given error") + + _ = os.Remove(fileName) + tmpPermCofig.Accounts = append(tmpPermCofig.Accounts, common.StringToAddress("0xed9d02e382b34818e88b88a309c7fe71e65f419d")) + blob, _ = json.Marshal(tmpPermCofig) + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + + _, err = ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "expected sub org depth not set error") + + _ = os.Remove(fileName) + tmpPermCofig.SubOrgBreadth.Set(big.NewInt(4)) + tmpPermCofig.SubOrgDepth.Set(big.NewInt(4)) + blob, _ = json.Marshal(tmpPermCofig) + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + + _, err = ptype.ParsePermissionConfig(d) + assert.True(t, err != nil, "expected contract address error") + + _ = os.Remove(fileName) + tmpPermCofig.InterfAddress = common.StringToAddress("0xed9d02e382b34818e88b88a309c7fe71e65f419d") + blob, _ = json.Marshal(tmpPermCofig) + if err := ioutil.WriteFile(fileName, blob, 0644); err != nil { + t.Fatal("Error writing new Node info to file", "fileName", fileName, "err", err) + } + permConfig, _ := ptype.ParsePermissionConfig(d) + assert.False(t, permConfig.IsEmpty(), "expected non empty object") +} + +func TestIsTransactionAllowed_V1(t *testing.T) { + testObject := typicalQuorumControlsAPI(t) + pcore.PermissionTransactionAllowedFunc = testObject.permCtrl.IsTransactionAllowed + var Acct1 = common.BytesToAddress([]byte("permission")) + var Acct2 = common.BytesToAddress([]byte("perm-test")) + pcore.SetDefaults(arbitraryNetworkAdminRole, arbitraryOrgAdminRole, false) + pcore.SetQIP714BlockReached() + pcore.SetNetworkBootUpCompleted() + pcore.OrgInfoMap = pcore.NewOrgCache(params.DEFAULT_ORGCACHE_SIZE) + pcore.RoleInfoMap = pcore.NewRoleCache(params.DEFAULT_ROLECACHE_SIZE) + pcore.AcctInfoMap = pcore.NewAcctCache(params.DEFAULT_ACCOUNTCACHE_SIZE) + + pcore.OrgInfoMap.UpsertOrg(arbitraryOrgAdminRole, "", arbitraryOrgAdminRole, big.NewInt(1), pcore.OrgApproved) + pcore.RoleInfoMap.UpsertRole(arbitraryOrgAdminRole, "ROLE1", false, false, pcore.Transact, true) + pcore.RoleInfoMap.UpsertRole(arbitraryOrgAdminRole, "ROLE2", false, false, pcore.ContractDeploy, true) + pcore.RoleInfoMap.UpsertRole(arbitraryOrgAdminRole, "ROLE3", false, false, pcore.FullAccess, true) + var Acct3 = common.BytesToAddress([]byte("permission-test1")) + var Acct4 = common.BytesToAddress([]byte("permission-test2")) + + pcore.AcctInfoMap.UpsertAccount(arbitraryOrgAdminRole, "ROLE1", Acct1, false, pcore.AcctActive) + pcore.AcctInfoMap.UpsertAccount(arbitraryOrgAdminRole, "ROLE2", Acct2, false, pcore.AcctActive) + pcore.AcctInfoMap.UpsertAccount(arbitraryOrgAdminRole, "ROLE3", Acct3, false, pcore.AcctActive) + + type args struct { + address common.Address + transactionType pcore.TransactionType + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Account with transact permission calling value transfer", + args: args{address: Acct1, transactionType: pcore.ValueTransferTxn}, + wantErr: false, + }, + { + name: "Account with transact permission calling value contract call transaction", + args: args{address: Acct1, transactionType: pcore.ContractCallTxn}, + wantErr: false, + }, + { + name: "Account with transact permission calling contract deploy", + args: args{address: Acct1, transactionType: pcore.ContractDeployTxn}, + wantErr: true, + }, + { + name: "Account with contract permission deploy calling value transfer", + args: args{address: Acct2, transactionType: pcore.ValueTransferTxn}, + wantErr: false, + }, + { + name: "Account with contract deploy permission calling value contract call transaction", + args: args{address: Acct2, transactionType: pcore.ContractCallTxn}, + wantErr: false, + }, + { + name: "Account with contract deploy permission calling contract deploy", + args: args{address: Acct2, transactionType: pcore.ContractDeployTxn}, + wantErr: false, + }, + { + name: "Account with full permission calling value transfer", + args: args{address: Acct3, transactionType: pcore.ValueTransferTxn}, + wantErr: false, + }, + { + name: "Account with full permission calling value contract call transaction", + args: args{address: Acct3, transactionType: pcore.ContractCallTxn}, + wantErr: false, + }, + { + name: "Account with full permission calling contract deploy", + args: args{address: Acct3, transactionType: pcore.ContractDeployTxn}, + wantErr: false, + }, + { + name: "un-permissioned account calling value transfer", + args: args{address: Acct4, transactionType: pcore.ValueTransferTxn}, + wantErr: true, + }, + { + name: "un-permissioned account calling contract call transaction", + args: args{address: Acct4, transactionType: pcore.ContractCallTxn}, + wantErr: true, + }, + { + name: "un-permissioned account calling contract deploy", + args: args{address: Acct4, transactionType: pcore.ContractDeployTxn}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := pcore.IsTransactionAllowed(tt.args.address, common.Address{}, nil, nil, nil, nil, tt.args.transactionType); (err != nil) != tt.wantErr { + t.Errorf("IsTransactionAllowed() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/permission/v1/backend.go b/permission/v1/backend.go new file mode 100644 index 0000000000..c2abe73541 --- /dev/null +++ b/permission/v1/backend.go @@ -0,0 +1,368 @@ +package v1 + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" + pb "github.com/ethereum/go-ethereum/permission/v1/bind" +) + +type Backend struct { + Ib ptype.InterfaceBackend + Contr *Init +} + +func (b *Backend) ManageAccountPermissions() error { + chAccessModified := make(chan *pb.AcctManagerAccountAccessModified) + chAccessRevoked := make(chan *pb.AcctManagerAccountAccessRevoked) + chStatusChanged := make(chan *pb.AcctManagerAccountStatusChanged) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + + if _, err := b.Contr.PermAcct.AcctManagerFilterer.WatchAccountAccessModified(opts, chAccessModified); err != nil { + return fmt.Errorf("failed AccountAccessModified: %v", err) + } + + if _, err := b.Contr.PermAcct.AcctManagerFilterer.WatchAccountAccessRevoked(opts, chAccessRevoked); err != nil { + return fmt.Errorf("failed AccountAccessRevoked: %v", err) + } + + if _, err := b.Contr.PermAcct.AcctManagerFilterer.WatchAccountStatusChanged(opts, chStatusChanged); err != nil { + return fmt.Errorf("failed AccountStatusChanged: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtAccessModified := <-chAccessModified: + core.AcctInfoMap.UpsertAccount(evtAccessModified.OrgId, evtAccessModified.RoleId, evtAccessModified.Account, evtAccessModified.OrgAdmin, core.AcctStatus(int(evtAccessModified.Status.Uint64()))) + + case evtAccessRevoked := <-chAccessRevoked: + core.AcctInfoMap.UpsertAccount(evtAccessRevoked.OrgId, evtAccessRevoked.RoleId, evtAccessRevoked.Account, evtAccessRevoked.OrgAdmin, core.AcctActive) + + case evtStatusChanged := <-chStatusChanged: + if ac, err := core.AcctInfoMap.GetAccount(evtStatusChanged.Account); ac != nil { + core.AcctInfoMap.UpsertAccount(evtStatusChanged.OrgId, ac.RoleId, evtStatusChanged.Account, ac.IsOrgAdmin, core.AcctStatus(int(evtStatusChanged.Status.Uint64()))) + } else { + log.Info("error fetching account information", "err", err) + } + case <-stopChan: + log.Info("quit account Contr watch") + return + } + } + }() + return nil +} + +func (b *Backend) ManageRolePermissions() error { + chRoleCreated := make(chan *pb.RoleManagerRoleCreated, 1) + chRoleRevoked := make(chan *pb.RoleManagerRoleRevoked, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + contract := b.Contr + + if _, err := contract.PermRole.RoleManagerFilterer.WatchRoleCreated(opts, chRoleCreated); err != nil { + return fmt.Errorf("failed WatchRoleCreated: %v", err) + } + + if _, err := contract.PermRole.RoleManagerFilterer.WatchRoleRevoked(opts, chRoleRevoked); err != nil { + return fmt.Errorf("failed WatchRoleRevoked: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtRoleCreated := <-chRoleCreated: + core.RoleInfoMap.UpsertRole(evtRoleCreated.OrgId, evtRoleCreated.RoleId, evtRoleCreated.IsVoter, evtRoleCreated.IsAdmin, core.AccessType(int(evtRoleCreated.BaseAccess.Uint64())), true) + + case evtRoleRevoked := <-chRoleRevoked: + if r, _ := core.RoleInfoMap.GetRole(evtRoleRevoked.OrgId, evtRoleRevoked.RoleId); r != nil { + core.RoleInfoMap.UpsertRole(evtRoleRevoked.OrgId, evtRoleRevoked.RoleId, r.IsVoter, r.IsAdmin, r.Access, false) + } else { + log.Error("Revoke role - cache is missing role", "org", evtRoleRevoked.OrgId, "role", evtRoleRevoked.RoleId) + } + case <-stopChan: + log.Info("quit role Contr watch") + return + } + } + }() + return nil +} + +func (b *Backend) ManageOrgPermissions() error { + chPendingApproval := make(chan *pb.OrgManagerOrgPendingApproval, 1) + chOrgApproved := make(chan *pb.OrgManagerOrgApproved, 1) + chOrgSuspended := make(chan *pb.OrgManagerOrgSuspended, 1) + chOrgReactivated := make(chan *pb.OrgManagerOrgSuspensionRevoked, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + contract := b.Contr + + if _, err := contract.PermOrg.OrgManagerFilterer.WatchOrgPendingApproval(opts, chPendingApproval); err != nil { + return fmt.Errorf("failed WatchOrgPendingApproval: %v", err) + } + + if _, err := contract.PermOrg.OrgManagerFilterer.WatchOrgApproved(opts, chOrgApproved); err != nil { + return fmt.Errorf("failed WatchOrgApproved: %v", err) + } + + if _, err := contract.PermOrg.OrgManagerFilterer.WatchOrgSuspended(opts, chOrgSuspended); err != nil { + return fmt.Errorf("failed WatchOrgSuspended: %v", err) + } + + if _, err := contract.PermOrg.OrgManagerFilterer.WatchOrgSuspensionRevoked(opts, chOrgReactivated); err != nil { + return fmt.Errorf("failed WatchOrgSuspensionRevoked: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtPendingApproval := <-chPendingApproval: + core.OrgInfoMap.UpsertOrg(evtPendingApproval.OrgId, evtPendingApproval.PorgId, evtPendingApproval.UltParent, evtPendingApproval.Level, core.OrgStatus(evtPendingApproval.Status.Uint64())) + + case evtOrgApproved := <-chOrgApproved: + core.OrgInfoMap.UpsertOrg(evtOrgApproved.OrgId, evtOrgApproved.PorgId, evtOrgApproved.UltParent, evtOrgApproved.Level, core.OrgApproved) + + case evtOrgSuspended := <-chOrgSuspended: + core.OrgInfoMap.UpsertOrg(evtOrgSuspended.OrgId, evtOrgSuspended.PorgId, evtOrgSuspended.UltParent, evtOrgSuspended.Level, core.OrgSuspended) + + case evtOrgReactivated := <-chOrgReactivated: + core.OrgInfoMap.UpsertOrg(evtOrgReactivated.OrgId, evtOrgReactivated.PorgId, evtOrgReactivated.UltParent, evtOrgReactivated.Level, core.OrgApproved) + case <-stopChan: + log.Info("quit org Contr watch") + return + } + } + }() + return nil +} + +func (b *Backend) ManageNodePermissions() error { + chNodeApproved := make(chan *pb.NodeManagerNodeApproved, 1) + chNodeProposed := make(chan *pb.NodeManagerNodeProposed, 1) + chNodeDeactivated := make(chan *pb.NodeManagerNodeDeactivated, 1) + chNodeActivated := make(chan *pb.NodeManagerNodeActivated, 1) + chNodeBlacklisted := make(chan *pb.NodeManagerNodeBlacklisted) + chNodeRecoveryInit := make(chan *pb.NodeManagerNodeRecoveryInitiated, 1) + chNodeRecoveryDone := make(chan *pb.NodeManagerNodeRecoveryCompleted, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + contract := b.Contr + + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeApproved(opts, chNodeApproved); err != nil { + return fmt.Errorf("failed WatchNodeApproved: %v", err) + } + + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeProposed(opts, chNodeProposed); err != nil { + return fmt.Errorf("failed WatchNodeProposed: %v", err) + } + + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeDeactivated(opts, chNodeDeactivated); err != nil { + return fmt.Errorf("failed NodeDeactivated: %v", err) + } + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeActivated(opts, chNodeActivated); err != nil { + return fmt.Errorf("failed WatchNodeActivated: %v", err) + } + + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeBlacklisted(opts, chNodeBlacklisted); err != nil { + return fmt.Errorf("failed NodeBlacklisting: %v", err) + } + + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeRecoveryInitiated(opts, chNodeRecoveryInit); err != nil { + return fmt.Errorf("failed NodeRecoveryInitiated: %v", err) + } + + if _, err := contract.PermNode.NodeManagerFilterer.WatchNodeRecoveryCompleted(opts, chNodeRecoveryDone); err != nil { + return fmt.Errorf("failed NodeRecoveryCompleted: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtNodeApproved := <-chNodeApproved: + err := ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), evtNodeApproved.EnodeId, ptype.NodeAdd, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + core.NodeInfoMap.UpsertNode(evtNodeApproved.OrgId, evtNodeApproved.EnodeId, core.NodeApproved) + + case evtNodeProposed := <-chNodeProposed: + core.NodeInfoMap.UpsertNode(evtNodeProposed.OrgId, evtNodeProposed.EnodeId, core.NodePendingApproval) + + case evtNodeDeactivated := <-chNodeDeactivated: + err := ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), evtNodeDeactivated.EnodeId, ptype.NodeDelete, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + core.NodeInfoMap.UpsertNode(evtNodeDeactivated.OrgId, evtNodeDeactivated.EnodeId, core.NodeDeactivated) + + case evtNodeActivated := <-chNodeActivated: + err := ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), evtNodeActivated.EnodeId, ptype.NodeAdd, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + core.NodeInfoMap.UpsertNode(evtNodeActivated.OrgId, evtNodeActivated.EnodeId, core.NodeApproved) + + case evtNodeBlacklisted := <-chNodeBlacklisted: + core.NodeInfoMap.UpsertNode(evtNodeBlacklisted.OrgId, evtNodeBlacklisted.EnodeId, core.NodeBlackListed) + err := ptype.UpdateDisallowedNodes(b.Ib.DataDir(), evtNodeBlacklisted.EnodeId, ptype.NodeAdd) + log.Error("error updating disallowed-nodes.json", "err", err) + err = ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), evtNodeBlacklisted.EnodeId, ptype.NodeDelete, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + + case evtNodeRecoveryInit := <-chNodeRecoveryInit: + core.NodeInfoMap.UpsertNode(evtNodeRecoveryInit.OrgId, evtNodeRecoveryInit.EnodeId, core.NodeRecoveryInitiated) + + case evtNodeRecoveryDone := <-chNodeRecoveryDone: + core.NodeInfoMap.UpsertNode(evtNodeRecoveryDone.OrgId, evtNodeRecoveryDone.EnodeId, core.NodeApproved) + err := ptype.UpdateDisallowedNodes(b.Ib.DataDir(), evtNodeRecoveryDone.EnodeId, ptype.NodeDelete) + log.Error("error updating disallowed-nodes.json", "err", err) + err = ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), evtNodeRecoveryDone.EnodeId, ptype.NodeAdd, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + + case <-stopChan: + log.Info("quit Node Contr watch") + return + } + } + }() + return nil +} + +func (b *Backend) MonitorNetworkBootUp() error { + netWorkBootCh := make(chan *pb.PermImplPermissionsInitialized, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + + if _, err := b.Contr.PermImpl.PermImplFilterer.WatchPermissionsInitialized(opts, netWorkBootCh); err != nil { + return fmt.Errorf("failed WatchPermissionsInitialized: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtMetworkBootUpCompleted := <-netWorkBootCh: + if evtMetworkBootUpCompleted.NetworkBootStatus { + core.SetNetworkBootUpCompleted() + } + return + case <-stopChan: + log.Info("quit implementation contract network boot watch") + return + } + } + }() + return nil +} + +func getInterfaceContractSession(permInterfaceInstance *pb.PermInterface, contractAddress common.Address, backend bind.ContractBackend) (*pb.PermInterfaceSession, error) { + if err := ptype.BindContract(&permInterfaceInstance, func() (interface{}, error) { return pb.NewPermInterface(contractAddress, backend) }); err != nil { + return nil, err + } + ps := &pb.PermInterfaceSession{ + Contract: permInterfaceInstance, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + return ps, nil +} + +func getBackend(contractBackend ptype.ContractBackend) (*PermissionModelV1, error) { + backend := PermissionModelV1{ContractBackend: contractBackend} + ps, err := getInterfaceContractSession(backend.PermInterf, contractBackend.PermConfig.InterfAddress, contractBackend.EthClnt) + if err != nil { + return nil, err + } + backend.PermInterfSession = ps + return &backend, nil +} + +func getBackendWithTransactOpts(contractBackend ptype.ContractBackend, transactOpts *bind.TransactOpts) (*PermissionModelV1, error) { + backend, err := getBackend(contractBackend) + if err != nil { + return nil, err + } + backend.PermInterfSession.TransactOpts = *transactOpts + + return backend, nil +} + +func (b *Backend) GetRoleService(transactOpts *bind.TransactOpts, roleBackend ptype.ContractBackend) (ptype.RoleService, error) { + backEnd, err := getBackendWithTransactOpts(roleBackend, transactOpts) + if err != nil { + return nil, err + } + return &Role{Backend: backEnd}, nil + +} + +func (b *Backend) GetOrgService(transactOpts *bind.TransactOpts, orgBackend ptype.ContractBackend) (ptype.OrgService, error) { + backEnd, err := getBackendWithTransactOpts(orgBackend, transactOpts) + if err != nil { + return nil, err + } + return &Org{Backend: backEnd}, nil + +} + +func (b *Backend) GetNodeService(transactOpts *bind.TransactOpts, nodeBackend ptype.ContractBackend) (ptype.NodeService, error) { + backEnd, err := getBackendWithTransactOpts(nodeBackend, transactOpts) + if err != nil { + return nil, err + } + return &Node{Backend: backEnd}, nil + +} + +func (b *Backend) GetAccountService(transactOpts *bind.TransactOpts, accountBackend ptype.ContractBackend) (ptype.AccountService, error) { + backEnd, err := getBackendWithTransactOpts(accountBackend, transactOpts) + if err != nil { + return nil, err + } + return &Account{Backend: backEnd}, nil + +} + +func (b *Backend) GetAuditService(auditBackend ptype.ContractBackend) (ptype.AuditService, error) { + backEnd, err := getBackend(auditBackend) + if err != nil { + return nil, err + } + return &Audit{Backend: backEnd}, nil + +} + +func (b *Backend) GetControlService(controlBackend ptype.ContractBackend) (ptype.ControlService, error) { + return &Control{}, nil +} diff --git a/permission/v1/bind/accounts.go b/permission/v1/bind/accounts.go new file mode 100644 index 0000000000..02940eae44 --- /dev/null +++ b/permission/v1/bind/accounts.go @@ -0,0 +1,935 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// AcctManagerABI is the input ABI used to generate the binding from. +const AcctManagerABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_adminRole\",\"type\":\"bool\"}],\"name\":\"assignAccountRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"removeExistingAdmin\",\"outputs\":[{\"name\":\"voterUpdate\",\"type\":\"bool\"},{\"name\":\"account\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"getAccountDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfAccounts\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"validateAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"getAccountRole\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateAccountStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"orgAdminExists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_aIndex\",\"type\":\"uint256\"}],\"name\":\"getAccountDetailsFromIndex\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"addNewAdmin\",\"outputs\":[{\"name\":\"voterUpdate\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"}],\"name\":\"setDefaults\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"assignAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"checkOrgAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgAdmin\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"AccountAccessModified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgAdmin\",\"type\":\"bool\"}],\"name\":\"AccountAccessRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"AccountStatusChanged\",\"type\":\"event\"}]" + +var AcctManagerParsedABI, _ = abi.JSON(strings.NewReader(AcctManagerABI)) + +// AcctManagerBin is the compiled bytecode used for deploying new contracts. +var AcctManagerBin = "0x608060405234801561001057600080fd5b50604051602080613a0c8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556139aa806100626000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806384b7a84a1161008c578063c214e5e511610066578063c214e5e5146105eb578063cef7f6af14610662578063e3483a9d14610720578063e8b42bf4146107ee576100cf565b806384b7a84a146104ad578063950145cf1461052a578063b2018568146105ce576100cf565b8063143a5604146100d45780631d09dc93146101a65780632aceb53414610237578063309e36ef146103665780636b568d761461038057806381d66b2314610412575b600080fd5b6101a4600480360360808110156100ea57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561011457600080fd5b82018360208201111561012657600080fd5b803590602001918460018302840111600160201b8311171561014757600080fd5b919390929091602081019035600160201b81111561016457600080fd5b82018360208201111561017657600080fd5b803590602001918460018302840111600160201b8311171561019757600080fd5b9193509150351515610927565b005b610214600480360360208110156101bc57600080fd5b810190602081018135600160201b8111156101d657600080fd5b8201836020820111156101e857600080fd5b803590602001918460018302840111600160201b8311171561020957600080fd5b509092509050610d25565b6040805192151583526001600160a01b0390911660208301528051918290030190f35b61025d6004803603602081101561024d57600080fd5b50356001600160a01b03166112b1565b60405180866001600160a01b03166001600160a01b03168152602001806020018060200185815260200184151515158152602001838103835287818151815260200191508051906020019080838360005b838110156102c65781810151838201526020016102ae565b50505050905090810190601f1680156102f35780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b8381101561032657818101518382015260200161030e565b50505050905090810190601f1680156103535780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b61036e611509565b60408051918252519081900360200190f35b6103fe6004803603604081101561039657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156103c057600080fd5b8201836020820111156103d257600080fd5b803590602001918460018302840111600160201b831117156103f357600080fd5b509092509050611510565b604080519115158252519081900360200190f35b6104386004803603602081101561042857600080fd5b50356001600160a01b031661166b565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561047257818101518382015260200161045a565b50505050905090810190601f16801561049f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101a4600480360360608110156104c357600080fd5b810190602081018135600160201b8111156104dd57600080fd5b8201836020820111156104ef57600080fd5b803590602001918460018302840111600160201b8311171561051057600080fd5b91935091506001600160a01b0381351690602001356117c1565b6103fe6004803603602081101561054057600080fd5b810190602081018135600160201b81111561055a57600080fd5b82018360208201111561056c57600080fd5b803590602001918460018302840111600160201b8311171561058d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611ef4945050505050565b61025d600480360360208110156105e457600080fd5b503561206f565b6103fe6004803603604081101561060157600080fd5b810190602081018135600160201b81111561061b57600080fd5b82018360208201111561062d57600080fd5b803590602001918460018302840111600160201b8311171561064e57600080fd5b9193509150356001600160a01b0316612259565b6101a46004803603604081101561067857600080fd5b810190602081018135600160201b81111561069257600080fd5b8201836020820111156106a457600080fd5b803590602001918460018302840111600160201b831117156106c557600080fd5b919390929091602081019035600160201b8111156106e257600080fd5b8201836020820111156106f457600080fd5b803590602001918460018302840111600160201b8311171561071557600080fd5b5090925090506128ab565b6101a46004803603608081101561073657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561076057600080fd5b82018360208201111561077257600080fd5b803590602001918460018302840111600160201b8311171561079357600080fd5b919390929091602081019035600160201b8111156107b057600080fd5b8201836020820111156107c257600080fd5b803590602001918460018302840111600160201b831117156107e357600080fd5b919350915035612997565b6103fe6004803603606081101561080457600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561082e57600080fd5b82018360208201111561084057600080fd5b803590602001918460018302840111600160201b8311171561086157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156108b357600080fd5b8201836020820111156108c557600080fd5b803590602001918460018302840111600160201b831117156108e657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612d09945050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561097457600080fd5b505afa158015610988573d6000803e3d6000fd5b505050506040513d602081101561099e57600080fd5b50516001600160a01b031633146109f35760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015610a765780601f10610a4b57610100808354040283529160200191610a76565b820191906000526020600020905b815481529060010190602001808311610a5957829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014158015610c6457506040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015610b715780601f10610b4657610100808354040283529160200191610b71565b820191906000526020600020905b815481529060010190602001808311610b5457829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040526040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610c19578181015183820152602001610c01565b50505050905090810190601f168015610c465780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012014155b1515610ca457604051600160e51b62461bcd0281526004018080602001828103825260408152602001806138da6040913960400191505060405180910390fd5b610d1d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250600292508791506132709050565b505050505050565b6000805460408051600160e41b62e32cf9028152905183926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610d6c57600080fd5b505afa158015610d80573d6000803e3d6000fd5b505050506040513d6020811015610d9657600080fd5b50516001600160a01b03163314610deb5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b610e2a84848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611ef492505050565b156112a3576000610eb260066000878760405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a90046001600160a01b031661364a565b90506006600182815481101515610ec557fe5b9060005260206000209060050201600301819055506000600182815481101515610eeb57fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776600182815481101515610f4357fe5b6000918252602090912060059091020154600180546001600160a01b039092169184908110610f6e57fe5b9060005260206000209060050201600101600184815481101515610f8e57fe5b9060005260206000209060050201600201600185815481101515610fae57fe5b60009182526020909120600460059092020101546001805460ff9092169187908110610fd657fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156110895780601f1061105e57610100808354040283529160200191611089565b820191906000526020600020905b81548152906001019060200180831161106c57829003601f168201915b50508381038252865460026000196101006001841615020190911604808252602090910190879080156110fd5780601f106110d2576101008083540402835291602001916110fd565b820191906000526020600020905b8154815290600101906020018083116110e057829003601f168201915b505097505050505050505060405180910390a160408051602080820190815260048054600260001961010060018416150201909116049383018490529290918291606090910190849080156111935780601f1061116857610100808354040283529160200191611193565b820191906000526020600020905b81548152906001019060200180831161117657829003601f168201915b505092505050604051602081830303815290604052805190602001206001828154811015156111be57fe5b6000918252602091829020604080518085019485526002600590940290920183018054600019610100600183161502011693909304908201819052919291829160600190849080156112515780601f1061122657610100808354040283529160200191611251565b820191906000526020600020905b81548152906001019060200180831161123457829003601f168201915b505092505050604051602081830303815290604052805190602001201460018281548110151561127d57fe5b60009182526020909120600590910201549093506001600160a01b031691506112aa9050565b5060009050805b9250929050565b6001600160a01b038116600090815260026020526040812054606090819083908190151561131857505060408051808201825260048152600160e01b634e4f4e45026020808301919091528251908101909252600080835286955090935090915080611500565b60006113238761364a565b905060018181548110151561133457fe5b6000918252602090912060059091020154600180546001600160a01b03909216918390811061135f57fe5b906000526020600020906005020160010160018381548110151561137f57fe5b906000526020600020906005020160020160018481548110151561139f57fe5b9060005260206000209060050201600301546001858154811015156113c057fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff9092169286919083018282801561145f5780601f106114345761010080835404028352916020019161145f565b820191906000526020600020905b81548152906001019060200180831161144257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156114ed5780601f106114c2576101008083540402835291602001916114ed565b820191906000526020600020905b8154815290600101906020018083116114d057829003601f168201915b5050505050925095509550955095509550505b91939590929450565b6001545b90565b6001600160a01b038316600090815260026020526040812054151561153757506001611664565b60006115428561364a565b9050838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001206001828154811015156115a657fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156116435780601f1061161857610100808354040283529160200191611643565b820191906000526020600020905b81548152906001019060200180831161162657829003601f168201915b50509250505060405160208183030381529060405280519060200120149150505b9392505050565b6001600160a01b03811660009081526002602052604090205460609015156116b157506040805180820190915260048152600160e01b634e4f4e450260208201526117bc565b60006116bc8361364a565b90506001818154811015156116cd57fe5b906000526020600020906005020160030154600014151561179b5760018054829081106116f657fe5b600091825260209182902060026005909202018101805460408051601f60001961010060018616150201909316949094049182018590048502840185019052808352919290919083018282801561178e5780601f106117635761010080835404028352916020019161178e565b820191906000526020600020905b81548152906001019060200180831161177157829003601f168201915b50505050509150506117bc565b50506040805180820190915260048152600160e01b634e4f4e450260208201525b919050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561180e57600080fd5b505afa158015611822573d6000803e3d6000fd5b505050506040513d602081101561183857600080fd5b50516001600160a01b0316331461188d5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506001600160a01b0387168152600260205260409020548693501515915061193290505760408051600160e51b62461bcd02815260206004820152601760248201527f6163636f756e7420646f6573206e6f7420657869737473000000000000000000604482015290519081900360640190fd5b816040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561197357818101518382015260200161195b565b50505050905090810190601f1680156119a05780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060016119c68361364a565b815481106119d057fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611a6d5780601f10611a4257610100808354040283529160200191611a6d565b820191906000526020600020905b815481529060010190602001808311611a5057829003601f168201915b50509250505060405160208183030381529060405280519060200120141515611ae05760408051600160e51b62461bcd02815260206004820152601860248201527f6163636f756e7420696e20646966666572656e74206f72670000000000000000604482015290519081900360640190fd5b600083118015611af05750600683105b1515611b465760408051600160e51b62461bcd02815260206004820152601d60248201527f696e76616c696420737461747573206368616e67652072657175657374000000604482015290519081900360640190fd5b611b948487878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602081019091529081529250612d09915050565b151560011415611bd857604051600160e51b62461bcd02815260040180806020018281038252603181526020018061394e6031913960400191505060405180910390fd5b60008360011415611c55576001611bee8661364a565b81548110611bf857fe5b9060005260206000209060050201600301546002141515611c4d57604051600160e51b62461bcd0281526004018080602001828103825260398152602001806138796039913960400191505060405180910390fd5b506004611e3e565b8360021415611cd0576001611c698661364a565b81548110611c7357fe5b9060005260206000209060050201600301546004141515611cc857604051600160e51b62461bcd02815260040180806020018281038252603c81526020018061383d603c913960400191505060405180910390fd5b506002611e3e565b8360031415611d4c576001611ce48661364a565b81548110611cee57fe5b906000526020600020906005020160030154600514151515611d4457604051600160e51b62461bcd0281526004018080602001828103825260388152602001806138056038913960400191505060405180910390fd5b506005611e3e565b8360041415611dc7576001611d608661364a565b81548110611d6a57fe5b9060005260206000209060050201600301546005141515611dbf57604051600160e51b62461bcd02815260040180806020018281038252603481526020018061391a6034913960400191505060405180910390fd5b506007611e3e565b8360051415611e3e576001611ddb8661364a565b81548110611de557fe5b9060005260206000209060050201600301546007141515611e3a57604051600160e51b62461bcd0281526004018080602001828103825260388152602001806137cd6038913960400191505060405180910390fd5b5060025b806001611e4a8761364a565b81548110611e5457fe5b9060005260206000209060050201600301819055507f36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b258588888460405180856001600160a01b03166001600160a01b03168152602001806020018381526020018281038252858582818152602001925080828437600083820152604051601f909101601f191690920182900397509095505050505050a150505050505050565b6000806001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611f45578181015183820152602001611f2d565b50505050905090810190601f168015611f725780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b03161461206757600060066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611ff1578181015183820152602001611fd9565b50505050905090810190601f16801561201e5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316905061205c81613669565b6002149150506117bc565b506000919050565b600060608060008060018681548110151561208657fe5b6000918252602090912060059091020154600180546001600160a01b0390921691889081106120b157fe5b90600052602060002090600502016001016001888154811015156120d157fe5b90600052602060002090600502016002016001898154811015156120f157fe5b90600052602060002090600502016003015460018a81548110151561211257fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff909216928691908301828280156121b15780601f10612186576101008083540402835291602001916121b1565b820191906000526020600020905b81548152906001019060200180831161219457829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529599508894509250840190508282801561223f5780601f106122145761010080835404028352916020019161223f565b820191906000526020600020905b81548152906001019060200180831161222257829003601f168201915b505050505092509450945094509450945091939590929450565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156122a857600080fd5b505afa1580156122bc573d6000803e3d6000fd5b505050506040513d60208110156122d257600080fd5b50516001600160a01b031633146123275760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60606123328361166b565b9050600061233f84613669565b9050600061234c8561364a565b604080516020808201908152600580546002600019610100600184161502019091160493830184905293945091829160600190849080156123ce5780601f106123a3576101008083540402835291602001916123ce565b820191906000526020600020905b8154815290600101906020018083116123b157829003601f168201915b50509250505060405160208183030381529060405280519060200120836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561242b578181015183820152602001612413565b50505050905090810190601f1680156124585780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001201480156124805750816001145b15612510578460066000898960405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b600260018281548110151561252157fe5b9060005260206000209060050201600301819055506001808281548110151561254657fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc7768560018381548110151561259f57fe5b90600052602060002090600502016001016001848154811015156125bf57fe5b90600052602060002090600502016002016001858154811015156125df57fe5b60009182526020909120600460059092020101546001805460ff909216918790811061260757fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156126ba5780601f1061268f576101008083540402835291602001916126ba565b820191906000526020600020905b81548152906001019060200180831161269d57829003601f168201915b505083810382528654600260001961010060018416150201909116048082526020909101908790801561272e5780601f106127035761010080835404028352916020019161272e565b820191906000526020600020905b81548152906001019060200180831161271157829003601f168201915b505097505050505050505060405180910390a160408051602080820190815260048054600260001961010060018416150201909116049383018490529290918291606090910190849080156127c45780601f10612799576101008083540402835291602001916127c4565b820191906000526020600020905b8154815290600101906020018083116127a757829003601f168201915b505092505050604051602081830303815290604052805190602001206001828154811015156127ef57fe5b6000918252602091829020604080518085019485526002600590940290920183018054600019610100600183161502011693909304908201819052919291829160600190849080156128825780601f1061285757610100808354040283529160200191612882565b820191906000526020600020905b81548152906001019060200180831161286557829003601f168201915b505092505050604051602081830303815290604052805190602001201493505050509392505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156128f857600080fd5b505afa15801561290c573d6000803e3d6000fd5b505050506040513d602081101561292257600080fd5b50516001600160a01b031633146129775760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b612983600485856136c6565b50612990600583836136c6565b5050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156129e457600080fd5b505afa1580156129f8573d6000803e3d6000fd5b505050506040513d6020811015612a0e57600080fd5b50516001600160a01b03163314612a635760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015612ae65780601f10612abb57610100808354040283529160200191612ae6565b820191906000526020600020905b815481529060010190602001808311612ac957829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001201480612c5057506040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015612bdf5780601f10612bb457610100808354040283529160200191612bdf565b820191906000526020600020905b815481529060010190602001808311612bc257829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120145b1515612c9057604051600160e51b62461bcd0281526004018080602001828103825260288152602001806138b26028913960400191505060405180910390fd5b610d1d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250879250600191506132709050565b60408051602080820190815260048054600260001961010060018416150201909116049383018490526000939092829160609091019084908015612d8e5780601f10612d6357610100808354040283529160200191612d8e565b820191906000526020600020905b815481529060010190602001808311612d7157829003601f168201915b50509250505060405160208183030381529060405280519060200120612db38561166b565b6040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612df3578181015183820152602001612ddb565b50505050905090810190601f168015612e205780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001201415613101576000612e4c8561364a565b9050836040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612e8f578181015183820152602001612e77565b50505050905090810190601f168015612ebc5780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120600182815481101515612ee657fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015612f835780601f10612f5857610100808354040283529160200191612f83565b820191906000526020600020905b815481529060010190602001808311612f6657829003601f168201915b5050925050506040516020818303038152906040528051906020012014806130f95750826040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612fe7578181015183820152602001612fcf565b50505050905090810190601f1680156130145780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561303e57fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156130db5780601f106130b0576101008083540402835291602001916130db565b820191906000526020600020905b8154815290600101906020018083116130be57829003601f168201915b50509250505060405160208183030381529060405280519060200120145b915050611664565b836001600160a01b031660066000856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015613150578181015183820152602001613138565b50505050905090810190601f16801561317d5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b031614806132685750836001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156132065781810151838201526020016131ee565b50505050905090810190601f1680156132335780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316145b949350505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156132bd57600080fd5b505afa1580156132d1573d6000803e3d6000fd5b505050506040513d60208110156132e757600080fd5b50516001600160a01b0316331461333c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60006133478661364a565b6001600160a01b038716600090815260026020526040902054909150156133f9578360018281548110151561337857fe5b9060005260206000209060050201600201908051906020019061339c929190613744565b50826001828154811015156133ad57fe5b906000526020600020906005020160030181905550816001828154811015156133d257fe5b60009182526020909120600590910201600401805460ff1916911515919091179055613514565b600380546001908101918290556001600160a01b03888116600081815260026020908152604080832096909655855160a0810187529283528281018b81529583018a905260608301899052871515608084015284548086018087559590925282517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6600590930292830180546001600160a01b0319169190951617845594518051949592946134d1937fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7909301929190910190613744565b50604082015180516134ed916002840191602090910190613744565b50606082015160038201556080909101516004909101805460ff1916911515919091179055505b7f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776868686858760405180866001600160a01b03166001600160a01b03168152602001806020018060200185151515158152602001848152602001838103835287818151815260200191508051906020019080838360005b838110156135a357818101518382015260200161358b565b50505050905090810190601f1680156135d05780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b838110156136035781810151838201526020016135eb565b50505050905090810190601f1680156136305780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a1505050505050565b6001600160a01b03166000908152600260205260409020546000190190565b6001600160a01b0381166000908152600260205260408120541515613690575060006117bc565b600061369b8361364a565b90506001818154811015156136ac57fe5b906000526020600020906005020160030154915050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106137075782800160ff19823516178555613734565b82800160010185558215613734579182015b82811115613734578235825591602001919060010190613719565b506137409291506137b2565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061378557805160ff1916838001178555613734565b82800160010185558215613734579182015b82811115613734578251825591602001919060010190613797565b61150d91905b8082111561374057600081556001016137b856fe6163636f756e74207265636f76657279206e6f7420696e697469617465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e7420697320616c726561647920626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e2073757370656e646564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e20616374697665207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e6563616e2062652063616c6c656420746f2061737369676e2061646d696e20726f6c6573206f6e6c7963616e6e6f742062652063616c6c65642066726f2061737369676e696e67206f72672061646d696e20616e64206e6574776f726b2061646d696e20726f6c65736163636f756e74206973206e6f7420626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e65737461747573206368616e6765206e6f7420706f737369626c6520666f72206f72672061646d696e206163636f756e7473a165627a7a723058203af90e45bb57e7b90add2e34a684e4b9db8ab4db25d7f2ef46dca296b60e11490029" + +// DeployAcctManager deploys a new Ethereum contract, binding an instance of AcctManager to it. +func DeployAcctManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *AcctManager, error) { + parsed, err := abi.JSON(strings.NewReader(AcctManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(AcctManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &AcctManager{AcctManagerCaller: AcctManagerCaller{contract: contract}, AcctManagerTransactor: AcctManagerTransactor{contract: contract}, AcctManagerFilterer: AcctManagerFilterer{contract: contract}}, nil +} + +// AcctManager is an auto generated Go binding around an Ethereum contract. +type AcctManager struct { + AcctManagerCaller // Read-only binding to the contract + AcctManagerTransactor // Write-only binding to the contract + AcctManagerFilterer // Log filterer for contract events +} + +// AcctManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type AcctManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AcctManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type AcctManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AcctManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type AcctManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AcctManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type AcctManagerSession struct { + Contract *AcctManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// AcctManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type AcctManagerCallerSession struct { + Contract *AcctManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// AcctManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type AcctManagerTransactorSession struct { + Contract *AcctManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// AcctManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type AcctManagerRaw struct { + Contract *AcctManager // Generic contract binding to access the raw methods on +} + +// AcctManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type AcctManagerCallerRaw struct { + Contract *AcctManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// AcctManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type AcctManagerTransactorRaw struct { + Contract *AcctManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewAcctManager creates a new instance of AcctManager, bound to a specific deployed contract. +func NewAcctManager(address common.Address, backend bind.ContractBackend) (*AcctManager, error) { + contract, err := bindAcctManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &AcctManager{AcctManagerCaller: AcctManagerCaller{contract: contract}, AcctManagerTransactor: AcctManagerTransactor{contract: contract}, AcctManagerFilterer: AcctManagerFilterer{contract: contract}}, nil +} + +// NewAcctManagerCaller creates a new read-only instance of AcctManager, bound to a specific deployed contract. +func NewAcctManagerCaller(address common.Address, caller bind.ContractCaller) (*AcctManagerCaller, error) { + contract, err := bindAcctManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &AcctManagerCaller{contract: contract}, nil +} + +// NewAcctManagerTransactor creates a new write-only instance of AcctManager, bound to a specific deployed contract. +func NewAcctManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*AcctManagerTransactor, error) { + contract, err := bindAcctManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &AcctManagerTransactor{contract: contract}, nil +} + +// NewAcctManagerFilterer creates a new log filterer instance of AcctManager, bound to a specific deployed contract. +func NewAcctManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*AcctManagerFilterer, error) { + contract, err := bindAcctManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &AcctManagerFilterer{contract: contract}, nil +} + +// bindAcctManager binds a generic wrapper to an already deployed contract. +func bindAcctManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(AcctManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_AcctManager *AcctManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _AcctManager.Contract.AcctManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_AcctManager *AcctManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AcctManager.Contract.AcctManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_AcctManager *AcctManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AcctManager.Contract.AcctManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_AcctManager *AcctManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _AcctManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_AcctManager *AcctManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AcctManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_AcctManager *AcctManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AcctManager.Contract.contract.Transact(opts, method, params...) +} + +// CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. +// +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +func (_AcctManager *AcctManagerCaller) CheckOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "checkOrgAdmin", _account, _orgId, _ultParent) + return *ret0, err +} + +// CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. +// +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +func (_AcctManager *AcctManagerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { + return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) +} + +// CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. +// +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +func (_AcctManager *AcctManagerCallerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { + return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) +} + +// GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. +// +// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCaller) GetAccountDetails(opts *bind.CallOpts, _account common.Address) (common.Address, string, string, *big.Int, bool, error) { + var ( + ret0 = new(common.Address) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(bool) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _AcctManager.contract.Call(opts, out, "getAccountDetails", _account) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. +// +// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) +} + +// GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. +// +// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCallerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) +} + +// GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. +// +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCaller) GetAccountDetailsFromIndex(opts *bind.CallOpts, _aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + var ( + ret0 = new(common.Address) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(bool) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _AcctManager.contract.Call(opts, out, "getAccountDetailsFromIndex", _aIndex) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. +// +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) +} + +// GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. +// +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCallerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) +} + +// GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. +// +// Solidity: function getAccountRole(address _account) constant returns(string) +func (_AcctManager *AcctManagerCaller) GetAccountRole(opts *bind.CallOpts, _account common.Address) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "getAccountRole", _account) + return *ret0, err +} + +// GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. +// +// Solidity: function getAccountRole(address _account) constant returns(string) +func (_AcctManager *AcctManagerSession) GetAccountRole(_account common.Address) (string, error) { + return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) +} + +// GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. +// +// Solidity: function getAccountRole(address _account) constant returns(string) +func (_AcctManager *AcctManagerCallerSession) GetAccountRole(_account common.Address) (string, error) { + return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) +} + +// GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. +// +// Solidity: function getNumberOfAccounts() constant returns(uint256) +func (_AcctManager *AcctManagerCaller) GetNumberOfAccounts(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "getNumberOfAccounts") + return *ret0, err +} + +// GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. +// +// Solidity: function getNumberOfAccounts() constant returns(uint256) +func (_AcctManager *AcctManagerSession) GetNumberOfAccounts() (*big.Int, error) { + return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) +} + +// GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. +// +// Solidity: function getNumberOfAccounts() constant returns(uint256) +func (_AcctManager *AcctManagerCallerSession) GetNumberOfAccounts() (*big.Int, error) { + return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) +} + +// OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. +// +// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCaller) OrgAdminExists(opts *bind.CallOpts, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "orgAdminExists", _orgId) + return *ret0, err +} + +// OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. +// +// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerSession) OrgAdminExists(_orgId string) (bool, error) { + return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) +} + +// OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. +// +// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCallerSession) OrgAdminExists(_orgId string) (bool, error) { + return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "validateAccount", _account, _orgId) + return *ret0, err +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) +} + +// AddNewAdmin is a paid mutator transaction binding the contract method 0xc214e5e5. +// +// Solidity: function addNewAdmin(string _orgId, address _account) returns(bool voterUpdate) +func (_AcctManager *AcctManagerTransactor) AddNewAdmin(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "addNewAdmin", _orgId, _account) +} + +// AddNewAdmin is a paid mutator transaction binding the contract method 0xc214e5e5. +// +// Solidity: function addNewAdmin(string _orgId, address _account) returns(bool voterUpdate) +func (_AcctManager *AcctManagerSession) AddNewAdmin(_orgId string, _account common.Address) (*types.Transaction, error) { + return _AcctManager.Contract.AddNewAdmin(&_AcctManager.TransactOpts, _orgId, _account) +} + +// AddNewAdmin is a paid mutator transaction binding the contract method 0xc214e5e5. +// +// Solidity: function addNewAdmin(string _orgId, address _account) returns(bool voterUpdate) +func (_AcctManager *AcctManagerTransactorSession) AddNewAdmin(_orgId string, _account common.Address) (*types.Transaction, error) { + return _AcctManager.Contract.AddNewAdmin(&_AcctManager.TransactOpts, _orgId, _account) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x143a5604. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, bool _adminRole) returns() +func (_AcctManager *AcctManagerTransactor) AssignAccountRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string, _adminRole bool) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "assignAccountRole", _account, _orgId, _roleId, _adminRole) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x143a5604. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, bool _adminRole) returns() +func (_AcctManager *AcctManagerSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _adminRole bool) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAccountRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _adminRole) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x143a5604. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, bool _adminRole) returns() +func (_AcctManager *AcctManagerTransactorSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _adminRole bool) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAccountRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _adminRole) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0xe3483a9d. +// +// Solidity: function assignAdminRole(address _account, string _orgId, string _roleId, uint256 _status) returns() +func (_AcctManager *AcctManagerTransactor) AssignAdminRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string, _status *big.Int) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "assignAdminRole", _account, _orgId, _roleId, _status) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0xe3483a9d. +// +// Solidity: function assignAdminRole(address _account, string _orgId, string _roleId, uint256 _status) returns() +func (_AcctManager *AcctManagerSession) AssignAdminRole(_account common.Address, _orgId string, _roleId string, _status *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAdminRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _status) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0xe3483a9d. +// +// Solidity: function assignAdminRole(address _account, string _orgId, string _roleId, uint256 _status) returns() +func (_AcctManager *AcctManagerTransactorSession) AssignAdminRole(_account common.Address, _orgId string, _roleId string, _status *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAdminRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _status) +} + +// RemoveExistingAdmin is a paid mutator transaction binding the contract method 0x1d09dc93. +// +// Solidity: function removeExistingAdmin(string _orgId) returns(bool voterUpdate, address account) +func (_AcctManager *AcctManagerTransactor) RemoveExistingAdmin(opts *bind.TransactOpts, _orgId string) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "removeExistingAdmin", _orgId) +} + +// RemoveExistingAdmin is a paid mutator transaction binding the contract method 0x1d09dc93. +// +// Solidity: function removeExistingAdmin(string _orgId) returns(bool voterUpdate, address account) +func (_AcctManager *AcctManagerSession) RemoveExistingAdmin(_orgId string) (*types.Transaction, error) { + return _AcctManager.Contract.RemoveExistingAdmin(&_AcctManager.TransactOpts, _orgId) +} + +// RemoveExistingAdmin is a paid mutator transaction binding the contract method 0x1d09dc93. +// +// Solidity: function removeExistingAdmin(string _orgId) returns(bool voterUpdate, address account) +func (_AcctManager *AcctManagerTransactorSession) RemoveExistingAdmin(_orgId string) (*types.Transaction, error) { + return _AcctManager.Contract.RemoveExistingAdmin(&_AcctManager.TransactOpts, _orgId) +} + +// SetDefaults is a paid mutator transaction binding the contract method 0xcef7f6af. +// +// Solidity: function setDefaults(string _nwAdminRole, string _oAdminRole) returns() +func (_AcctManager *AcctManagerTransactor) SetDefaults(opts *bind.TransactOpts, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "setDefaults", _nwAdminRole, _oAdminRole) +} + +// SetDefaults is a paid mutator transaction binding the contract method 0xcef7f6af. +// +// Solidity: function setDefaults(string _nwAdminRole, string _oAdminRole) returns() +func (_AcctManager *AcctManagerSession) SetDefaults(_nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _AcctManager.Contract.SetDefaults(&_AcctManager.TransactOpts, _nwAdminRole, _oAdminRole) +} + +// SetDefaults is a paid mutator transaction binding the contract method 0xcef7f6af. +// +// Solidity: function setDefaults(string _nwAdminRole, string _oAdminRole) returns() +func (_AcctManager *AcctManagerTransactorSession) SetDefaults(_nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _AcctManager.Contract.SetDefaults(&_AcctManager.TransactOpts, _nwAdminRole, _oAdminRole) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_AcctManager *AcctManagerTransactor) UpdateAccountStatus(opts *bind.TransactOpts, _orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "updateAccountStatus", _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_AcctManager *AcctManagerSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.UpdateAccountStatus(&_AcctManager.TransactOpts, _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_AcctManager *AcctManagerTransactorSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.UpdateAccountStatus(&_AcctManager.TransactOpts, _orgId, _account, _action) +} + +// AcctManagerAccountAccessModifiedIterator is returned from FilterAccountAccessModified and is used to iterate over the raw logs and unpacked data for AccountAccessModified events raised by the AcctManager contract. +type AcctManagerAccountAccessModifiedIterator struct { + Event *AcctManagerAccountAccessModified // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AcctManagerAccountAccessModifiedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessModified) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessModified) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AcctManagerAccountAccessModifiedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AcctManagerAccountAccessModifiedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AcctManagerAccountAccessModified represents a AccountAccessModified event raised by the AcctManager contract. +type AcctManagerAccountAccessModified struct { + Account common.Address + OrgId string + RoleId string + OrgAdmin bool + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAccountAccessModified is a free log retrieval operation binding the contract event 0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776. +// +// Solidity: event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint256 _status) +func (_AcctManager *AcctManagerFilterer) FilterAccountAccessModified(opts *bind.FilterOpts) (*AcctManagerAccountAccessModifiedIterator, error) { + + logs, sub, err := _AcctManager.contract.FilterLogs(opts, "AccountAccessModified") + if err != nil { + return nil, err + } + return &AcctManagerAccountAccessModifiedIterator{contract: _AcctManager.contract, event: "AccountAccessModified", logs: logs, sub: sub}, nil +} + +var AccountAccessModifiedTopicHash = "0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776" + +// WatchAccountAccessModified is a free log subscription operation binding the contract event 0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776. +// +// Solidity: event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint256 _status) +func (_AcctManager *AcctManagerFilterer) WatchAccountAccessModified(opts *bind.WatchOpts, sink chan<- *AcctManagerAccountAccessModified) (event.Subscription, error) { + + logs, sub, err := _AcctManager.contract.WatchLogs(opts, "AccountAccessModified") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AcctManagerAccountAccessModified) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessModified", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAccountAccessModified is a log parse operation binding the contract event 0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776. +// +// Solidity: event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint256 _status) +func (_AcctManager *AcctManagerFilterer) ParseAccountAccessModified(log types.Log) (*AcctManagerAccountAccessModified, error) { + event := new(AcctManagerAccountAccessModified) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessModified", log); err != nil { + return nil, err + } + return event, nil +} + +// AcctManagerAccountAccessRevokedIterator is returned from FilterAccountAccessRevoked and is used to iterate over the raw logs and unpacked data for AccountAccessRevoked events raised by the AcctManager contract. +type AcctManagerAccountAccessRevokedIterator struct { + Event *AcctManagerAccountAccessRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AcctManagerAccountAccessRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AcctManagerAccountAccessRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AcctManagerAccountAccessRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AcctManagerAccountAccessRevoked represents a AccountAccessRevoked event raised by the AcctManager contract. +type AcctManagerAccountAccessRevoked struct { + Account common.Address + OrgId string + RoleId string + OrgAdmin bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAccountAccessRevoked is a free log retrieval operation binding the contract event 0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1. +// +// Solidity: event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin) +func (_AcctManager *AcctManagerFilterer) FilterAccountAccessRevoked(opts *bind.FilterOpts) (*AcctManagerAccountAccessRevokedIterator, error) { + + logs, sub, err := _AcctManager.contract.FilterLogs(opts, "AccountAccessRevoked") + if err != nil { + return nil, err + } + return &AcctManagerAccountAccessRevokedIterator{contract: _AcctManager.contract, event: "AccountAccessRevoked", logs: logs, sub: sub}, nil +} + +var AccountAccessRevokedTopicHash = "0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1" + +// WatchAccountAccessRevoked is a free log subscription operation binding the contract event 0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1. +// +// Solidity: event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin) +func (_AcctManager *AcctManagerFilterer) WatchAccountAccessRevoked(opts *bind.WatchOpts, sink chan<- *AcctManagerAccountAccessRevoked) (event.Subscription, error) { + + logs, sub, err := _AcctManager.contract.WatchLogs(opts, "AccountAccessRevoked") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AcctManagerAccountAccessRevoked) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAccountAccessRevoked is a log parse operation binding the contract event 0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1. +// +// Solidity: event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin) +func (_AcctManager *AcctManagerFilterer) ParseAccountAccessRevoked(log types.Log) (*AcctManagerAccountAccessRevoked, error) { + event := new(AcctManagerAccountAccessRevoked) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessRevoked", log); err != nil { + return nil, err + } + return event, nil +} + +// AcctManagerAccountStatusChangedIterator is returned from FilterAccountStatusChanged and is used to iterate over the raw logs and unpacked data for AccountStatusChanged events raised by the AcctManager contract. +type AcctManagerAccountStatusChangedIterator struct { + Event *AcctManagerAccountStatusChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AcctManagerAccountStatusChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountStatusChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountStatusChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AcctManagerAccountStatusChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AcctManagerAccountStatusChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AcctManagerAccountStatusChanged represents a AccountStatusChanged event raised by the AcctManager contract. +type AcctManagerAccountStatusChanged struct { + Account common.Address + OrgId string + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAccountStatusChanged is a free log retrieval operation binding the contract event 0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25. +// +// Solidity: event AccountStatusChanged(address _account, string _orgId, uint256 _status) +func (_AcctManager *AcctManagerFilterer) FilterAccountStatusChanged(opts *bind.FilterOpts) (*AcctManagerAccountStatusChangedIterator, error) { + + logs, sub, err := _AcctManager.contract.FilterLogs(opts, "AccountStatusChanged") + if err != nil { + return nil, err + } + return &AcctManagerAccountStatusChangedIterator{contract: _AcctManager.contract, event: "AccountStatusChanged", logs: logs, sub: sub}, nil +} + +var AccountStatusChangedTopicHash = "0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25" + +// WatchAccountStatusChanged is a free log subscription operation binding the contract event 0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25. +// +// Solidity: event AccountStatusChanged(address _account, string _orgId, uint256 _status) +func (_AcctManager *AcctManagerFilterer) WatchAccountStatusChanged(opts *bind.WatchOpts, sink chan<- *AcctManagerAccountStatusChanged) (event.Subscription, error) { + + logs, sub, err := _AcctManager.contract.WatchLogs(opts, "AccountStatusChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AcctManagerAccountStatusChanged) + if err := _AcctManager.contract.UnpackLog(event, "AccountStatusChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAccountStatusChanged is a log parse operation binding the contract event 0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25. +// +// Solidity: event AccountStatusChanged(address _account, string _orgId, uint256 _status) +func (_AcctManager *AcctManagerFilterer) ParseAccountStatusChanged(log types.Log) (*AcctManagerAccountStatusChanged, error) { + event := new(AcctManagerAccountStatusChanged) + if err := _AcctManager.contract.UnpackLog(event, "AccountStatusChanged", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v1/bind/nodes.go b/permission/v1/bind/nodes.go new file mode 100644 index 0000000000..11e607eab1 --- /dev/null +++ b/permission/v1/bind/nodes.go @@ -0,0 +1,1356 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// NodeManagerABI is the input ABI used to generate the binding from. +const NodeManagerABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateNodeStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"enodeId\",\"type\":\"string\"}],\"name\":\"getNodeDetails\",\"outputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_nodeStatus\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addOrgNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"approveNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_nodeIndex\",\"type\":\"uint256\"}],\"name\":\"getNodeDetailsFromIndex\",\"outputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_nodeStatus\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfNodes\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addAdminNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeDeactivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeActivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeBlacklisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeRecoveryInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeRecoveryCompleted\",\"type\":\"event\"}]" + +var NodeManagerParsedABI, _ = abi.JSON(strings.NewReader(NodeManagerABI)) + +// NodeManagerBin is the compiled bytecode used for deploying new contracts. +var NodeManagerBin = "0x608060405234801561001057600080fd5b5060405160208061250b8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556124a9806100626000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806397c07a9b1161005b57806397c07a9b1461041c578063a97a440614610439578063b81c806a146104f7578063e3b09d84146102a057610088565b80630cc501461461008d5780633f0e0e471461014d5780633f5e1a45146102a057806386bc36521461035e575b600080fd5b61014b600480360360608110156100a357600080fd5b810190602081018135600160201b8111156100bd57600080fd5b8201836020820111156100cf57600080fd5b803590602001918460018302840111600160201b831117156100f057600080fd5b919390929091602081019035600160201b81111561010d57600080fd5b82018360208201111561011f57600080fd5b803590602001918460018302840111600160201b8311171561014057600080fd5b919350915035610511565b005b6101bb6004803603602081101561016357600080fd5b810190602081018135600160201b81111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111600160201b831117156101b057600080fd5b509092509050610f21565b604051808060200180602001848152602001838103835286818151815260200191508051906020019080838360005b838110156102025781810151838201526020016101ea565b50505050905090810190601f16801561022f5780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838360005b8381101561026257818101518382015260200161024a565b50505050905090810190601f16801561028f5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b61014b600480360360408110156102b657600080fd5b810190602081018135600160201b8111156102d057600080fd5b8201836020820111156102e257600080fd5b803590602001918460018302840111600160201b8311171561030357600080fd5b919390929091602081019035600160201b81111561032057600080fd5b82018360208201111561033257600080fd5b803590602001918460018302840111600160201b8311171561035357600080fd5b5090925090506111f7565b61014b6004803603604081101561037457600080fd5b810190602081018135600160201b81111561038e57600080fd5b8201836020820111156103a057600080fd5b803590602001918460018302840111600160201b831117156103c157600080fd5b919390929091602081019035600160201b8111156103de57600080fd5b8201836020820111156103f057600080fd5b803590602001918460018302840111600160201b8311171561041157600080fd5b5090925090506115d0565b6101bb6004803603602081101561043257600080fd5b5035611af2565b61014b6004803603604081101561044f57600080fd5b810190602081018135600160201b81111561046957600080fd5b82018360208201111561047b57600080fd5b803590602001918460018302840111600160201b8311171561049c57600080fd5b919390929091602081019035600160201b8111156104b957600080fd5b8201836020820111156104cb57600080fd5b803590602001918460018302840111600160201b831117156104ec57600080fd5b509092509050611c81565b6104ff61205a565b60408051918252519081900360200190f35b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561055e57600080fd5b505afa158015610572573d6000803e3d6000fd5b505050506040513d602081101561058857600080fd5b50516001600160a01b031633146105dd5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b84848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b8381101561065a578181015183820152602001610642565b50505050905090810190601f1680156106875780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415156107075760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b61077a86868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061206192505050565b15156107ba57604051600160e51b62461bcd02815260040180806020018281038252602a8152602001806123e1602a913960400191505060405180910390fd5b81600114806107c95750816002145b806107d45750816003145b806107df5750816004145b806107ea5750816005145b151561082a57604051600160e51b62461bcd02815260040180806020018281038252602681526020018061242b6026913960400191505060405180910390fd5b81600114156109aa5761087286868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b6002146108b75760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b600360016108fa88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b8154811061090457fe5b9060005260206000209060030201600201819055507fc6c3720fe673e87bb26e06be713d514278aa94c3939cfe7c64b9bea4d486824a868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b8160021415610b2a576109f286868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b600314610a375760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b60026001610a7a88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610a8457fe5b9060005260206000209060030201600201819055507f49796be3ca168a59c8ae46c75a36a9bb3a84753d3e12a812f93ae010e783b14f868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b8160031415610c265760046001610b7688888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610b8057fe5b9060005260206000209060030201600201819055507f4714623279994517c446c8fb72c3fdaca26434da1e2490d3976fe0cd880cfa7a868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b8160041415610da657610c6e86868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b600414610cb35760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b60056001610cf688888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610d0057fe5b9060005260206000209060030201600201819055507ffd385c618a1e89d01fb9a21780846793e282e8bc0b60caf6ccb3e422d543fbfb868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b610de586868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b600514610e2a5760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b60026001610e6d88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610e7757fe5b9060005260206000209060030201600201819055507f787d7bc525e7c4658c64e3e456d974a1be21cc196e8162a4bf1337a12cb38dac868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15b505050505050565b606080600060026000836040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610f6b578181015183820152602001610f53565b50505050905090810190601f168015610f985780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001208152602001908152602001600020546000141561102857848460006040518060200160405280600081525092919082828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509699509197509195506111f0945050505050565b600061106986868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b905060018181548110151561107a57fe5b906000526020600020906003020160010160018281548110151561109a57fe5b90600052602060002090600302016000016001838154811015156110ba57fe5b60009182526020918290206002600390920201810154845460408051601f6000196101006001861615020190931694909404918201859004850284018501905280835290928591908301828280156111535780601f1061112857610100808354040283529160200191611153565b820191906000526020600020905b81548152906001019060200180831161113657829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959850879450925084019050828280156111e15780601f106111b6576101008083540402835291602001916111e1565b820191906000526020600020905b8154815290600101906020018083116111c457829003601f168201915b50505050509150935093509350505b9250925092565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561124457600080fd5b505afa158015611258573d6000803e3d6000fd5b505050506040513d602081101561126e57600080fd5b50516001600160a01b031633146112c35760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b83811015611340578181015183820152602001611328565b50505050905090810190601f16801561136d5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652908501959095525050500160002054156113ec5760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b6003805460010190819055604080516020808201908152918101879052600291600091899189918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506001604051806060016040528087878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250505090825250604080516020601f88018190048102820181019092528681529181019190879087908190840183828082843760009201829052509385525050600260209384015250835460018101808655948252908290208351805160039093029091019261151692849290910190612348565b50602082810151805161152f9260018501920190612348565b50604082015181600201555050507f0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561161d57600080fd5b505afa158015611631573d6000803e3d6000fd5b505050506040513d602081101561164757600080fd5b50516001600160a01b0316331461169c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b83811015611719578181015183820152602001611701565b50505050905090810190601f1680156117465780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415156117c65760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b61183985858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525061206192505050565b151561187957604051600160e51b62461bcd02815260040180806020018281038252602d815260200180612451602d913960400191505060405180910390fd5b6118b885858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b60011461190f5760408051600160e51b62461bcd02815260206004820152601c60248201527f6e6f7468696e672070656e64696e6720666f7220617070726f76616c00000000604482015290519081900360640190fd5b600061195086868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b9050600260018281548110151561196357fe5b9060005260206000209060030201600201819055507f0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d6001828154811015156119a857fe5b90600052602060002090600302016000016001838154811015156119c857fe5b9060005260206000209060030201600101604051808060200180602001838103835285818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611a665780601f10611a3b57610100808354040283529160200191611a66565b820191906000526020600020905b815481529060010190602001808311611a4957829003601f168201915b5050838103825284546002600019610100600184161502019091160480825260209091019085908015611ada5780601f10611aaf57610100808354040283529160200191611ada565b820191906000526020600020905b815481529060010190602001808311611abd57829003601f168201915b505094505050505060405180910390a1505050505050565b6060806000600184815481101515611b0657fe5b9060005260206000209060030201600101600185815481101515611b2657fe5b9060005260206000209060030201600001600186815481101515611b4657fe5b60009182526020918290206002600390920201810154845460408051601f600019610100600186161502019093169490940491820185900485028401850190528083529092859190830182828015611bdf5780601f10611bb457610100808354040283529160200191611bdf565b820191906000526020600020905b815481529060010190602001808311611bc257829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295985087945092508401905082828015611c6d5780601f10611c4257610100808354040283529160200191611c6d565b820191906000526020600020905b815481529060010190602001808311611c5057829003601f168201915b505050505091509250925092509193909250565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611cce57600080fd5b505afa158015611ce2573d6000803e3d6000fd5b505050506040513d6020811015611cf857600080fd5b50516001600160a01b03163314611d4d5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b83811015611dca578181015183820152602001611db2565b50505050905090810190601f168015611df75780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415611e765760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b6003805460010190819055604080516020808201908152918101879052600291600091899189918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506001604051806060016040528087878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250505090825250604080516020601f880181900481028201810190925286815291810191908790879081908401838280828437600092018290525093855250506001602093840181905285549081018087559583529183902084518051600390940290910193611fa093859350910190612348565b506020828101518051611fb99260018501920190612348565b50604082015181600201555050507fb1a7eec7dd1a516c3132d6d1f770758b19aa34c3a07c138caf662688b7e3556f858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b6003545b90565b6000816040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156120a457818101518382015260200161208c565b50505050905090810190601f1680156120d15780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060016120f7856122a0565b8154811061210157fe5b9060005260206000209060030201600101604051602001808060200182810382528381815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561219e5780601f106121735761010080835404028352916020019161219e565b820191906000526020600020905b81548152906001019060200180831161218157829003601f168201915b5050925050506040516020818303038152906040528051906020012014905092915050565b600060026000836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561220a5781810151838201526020016121f2565b50505050905090810190601f1680156122375780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054600014156122715750600061229b565b600161227c836122a0565b8154811061228657fe5b90600052602060002090600302016002015490505b919050565b6000600160026000846040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156122e95781810151838201526020016122d1565b50505050905090810190601f1680156123165780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061238957805160ff19168380011785556123b6565b828001600101855582156123b6579182015b828111156123b657825182559160200191906001019061239b565b506123c29291506123c6565b5090565b61205e91905b808211156123c257600081556001016123cc56fe656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f72676f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000696e76616c6964206f7065726174696f6e2e2077726f6e6720616374696f6e20706173736564656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f7267206964a165627a7a723058207ca0dd787547cf61d1f16df314986310b2a2c8f853fdca9e4a4c784046b0864c0029" + +// DeployNodeManager deploys a new Ethereum contract, binding an instance of NodeManager to it. +func DeployNodeManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *NodeManager, error) { + parsed, err := abi.JSON(strings.NewReader(NodeManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(NodeManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &NodeManager{NodeManagerCaller: NodeManagerCaller{contract: contract}, NodeManagerTransactor: NodeManagerTransactor{contract: contract}, NodeManagerFilterer: NodeManagerFilterer{contract: contract}}, nil +} + +// NodeManager is an auto generated Go binding around an Ethereum contract. +type NodeManager struct { + NodeManagerCaller // Read-only binding to the contract + NodeManagerTransactor // Write-only binding to the contract + NodeManagerFilterer // Log filterer for contract events +} + +// NodeManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type NodeManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NodeManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type NodeManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NodeManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type NodeManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NodeManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type NodeManagerSession struct { + Contract *NodeManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NodeManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type NodeManagerCallerSession struct { + Contract *NodeManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// NodeManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type NodeManagerTransactorSession struct { + Contract *NodeManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NodeManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type NodeManagerRaw struct { + Contract *NodeManager // Generic contract binding to access the raw methods on +} + +// NodeManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type NodeManagerCallerRaw struct { + Contract *NodeManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// NodeManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type NodeManagerTransactorRaw struct { + Contract *NodeManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewNodeManager creates a new instance of NodeManager, bound to a specific deployed contract. +func NewNodeManager(address common.Address, backend bind.ContractBackend) (*NodeManager, error) { + contract, err := bindNodeManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &NodeManager{NodeManagerCaller: NodeManagerCaller{contract: contract}, NodeManagerTransactor: NodeManagerTransactor{contract: contract}, NodeManagerFilterer: NodeManagerFilterer{contract: contract}}, nil +} + +// NewNodeManagerCaller creates a new read-only instance of NodeManager, bound to a specific deployed contract. +func NewNodeManagerCaller(address common.Address, caller bind.ContractCaller) (*NodeManagerCaller, error) { + contract, err := bindNodeManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &NodeManagerCaller{contract: contract}, nil +} + +// NewNodeManagerTransactor creates a new write-only instance of NodeManager, bound to a specific deployed contract. +func NewNodeManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*NodeManagerTransactor, error) { + contract, err := bindNodeManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &NodeManagerTransactor{contract: contract}, nil +} + +// NewNodeManagerFilterer creates a new log filterer instance of NodeManager, bound to a specific deployed contract. +func NewNodeManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*NodeManagerFilterer, error) { + contract, err := bindNodeManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &NodeManagerFilterer{contract: contract}, nil +} + +// bindNodeManager binds a generic wrapper to an already deployed contract. +func bindNodeManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(NodeManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_NodeManager *NodeManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _NodeManager.Contract.NodeManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_NodeManager *NodeManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NodeManager.Contract.NodeManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_NodeManager *NodeManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NodeManager.Contract.NodeManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_NodeManager *NodeManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _NodeManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_NodeManager *NodeManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NodeManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_NodeManager *NodeManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NodeManager.Contract.contract.Transact(opts, method, params...) +} + +// GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. +// +// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCaller) GetNodeDetails(opts *bind.CallOpts, enodeId string) (struct { + OrgId string + EnodeId string + NodeStatus *big.Int +}, error) { + ret := new(struct { + OrgId string + EnodeId string + NodeStatus *big.Int + }) + out := ret + err := _NodeManager.contract.Call(opts, out, "getNodeDetails", enodeId) + return *ret, err +} + +// GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. +// +// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +func (_NodeManager *NodeManagerSession) GetNodeDetails(enodeId string) (struct { + OrgId string + EnodeId string + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetails(&_NodeManager.CallOpts, enodeId) +} + +// GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. +// +// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCallerSession) GetNodeDetails(enodeId string) (struct { + OrgId string + EnodeId string + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetails(&_NodeManager.CallOpts, enodeId) +} + +// GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. +// +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCaller) GetNodeDetailsFromIndex(opts *bind.CallOpts, _nodeIndex *big.Int) (struct { + OrgId string + EnodeId string + NodeStatus *big.Int +}, error) { + ret := new(struct { + OrgId string + EnodeId string + NodeStatus *big.Int + }) + out := ret + err := _NodeManager.contract.Call(opts, out, "getNodeDetailsFromIndex", _nodeIndex) + return *ret, err +} + +// GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. +// +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +func (_NodeManager *NodeManagerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { + OrgId string + EnodeId string + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetailsFromIndex(&_NodeManager.CallOpts, _nodeIndex) +} + +// GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. +// +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCallerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { + OrgId string + EnodeId string + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetailsFromIndex(&_NodeManager.CallOpts, _nodeIndex) +} + +// GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. +// +// Solidity: function getNumberOfNodes() constant returns(uint256) +func (_NodeManager *NodeManagerCaller) GetNumberOfNodes(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _NodeManager.contract.Call(opts, out, "getNumberOfNodes") + return *ret0, err +} + +// GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. +// +// Solidity: function getNumberOfNodes() constant returns(uint256) +func (_NodeManager *NodeManagerSession) GetNumberOfNodes() (*big.Int, error) { + return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) +} + +// GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. +// +// Solidity: function getNumberOfNodes() constant returns(uint256) +func (_NodeManager *NodeManagerCallerSession) GetNumberOfNodes() (*big.Int, error) { + return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0xe3b09d84. +// +// Solidity: function addAdminNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) AddAdminNode(opts *bind.TransactOpts, _enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "addAdminNode", _enodeId, _orgId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0xe3b09d84. +// +// Solidity: function addAdminNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerSession) AddAdminNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddAdminNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0xe3b09d84. +// +// Solidity: function addAdminNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) AddAdminNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddAdminNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// AddNode is a paid mutator transaction binding the contract method 0xa97a4406. +// +// Solidity: function addNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) AddNode(opts *bind.TransactOpts, _enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "addNode", _enodeId, _orgId) +} + +// AddNode is a paid mutator transaction binding the contract method 0xa97a4406. +// +// Solidity: function addNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerSession) AddNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// AddNode is a paid mutator transaction binding the contract method 0xa97a4406. +// +// Solidity: function addNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) AddNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// AddOrgNode is a paid mutator transaction binding the contract method 0x3f5e1a45. +// +// Solidity: function addOrgNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) AddOrgNode(opts *bind.TransactOpts, _enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "addOrgNode", _enodeId, _orgId) +} + +// AddOrgNode is a paid mutator transaction binding the contract method 0x3f5e1a45. +// +// Solidity: function addOrgNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerSession) AddOrgNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddOrgNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// AddOrgNode is a paid mutator transaction binding the contract method 0x3f5e1a45. +// +// Solidity: function addOrgNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) AddOrgNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddOrgNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// ApproveNode is a paid mutator transaction binding the contract method 0x86bc3652. +// +// Solidity: function approveNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) ApproveNode(opts *bind.TransactOpts, _enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "approveNode", _enodeId, _orgId) +} + +// ApproveNode is a paid mutator transaction binding the contract method 0x86bc3652. +// +// Solidity: function approveNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerSession) ApproveNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.ApproveNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// ApproveNode is a paid mutator transaction binding the contract method 0x86bc3652. +// +// Solidity: function approveNode(string _enodeId, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) ApproveNode(_enodeId string, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.ApproveNode(&_NodeManager.TransactOpts, _enodeId, _orgId) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x0cc50146. +// +// Solidity: function updateNodeStatus(string _enodeId, string _orgId, uint256 _action) returns() +func (_NodeManager *NodeManagerTransactor) UpdateNodeStatus(opts *bind.TransactOpts, _enodeId string, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "updateNodeStatus", _enodeId, _orgId, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x0cc50146. +// +// Solidity: function updateNodeStatus(string _enodeId, string _orgId, uint256 _action) returns() +func (_NodeManager *NodeManagerSession) UpdateNodeStatus(_enodeId string, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _NodeManager.Contract.UpdateNodeStatus(&_NodeManager.TransactOpts, _enodeId, _orgId, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x0cc50146. +// +// Solidity: function updateNodeStatus(string _enodeId, string _orgId, uint256 _action) returns() +func (_NodeManager *NodeManagerTransactorSession) UpdateNodeStatus(_enodeId string, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _NodeManager.Contract.UpdateNodeStatus(&_NodeManager.TransactOpts, _enodeId, _orgId, _action) +} + +// NodeManagerNodeActivatedIterator is returned from FilterNodeActivated and is used to iterate over the raw logs and unpacked data for NodeActivated events raised by the NodeManager contract. +type NodeManagerNodeActivatedIterator struct { + Event *NodeManagerNodeActivated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeActivatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeActivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeActivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeActivatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeActivatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeActivated represents a NodeActivated event raised by the NodeManager contract. +type NodeManagerNodeActivated struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeActivated is a free log retrieval operation binding the contract event 0x49796be3ca168a59c8ae46c75a36a9bb3a84753d3e12a812f93ae010e783b14f. +// +// Solidity: event NodeActivated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeActivated(opts *bind.FilterOpts) (*NodeManagerNodeActivatedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeActivated") + if err != nil { + return nil, err + } + return &NodeManagerNodeActivatedIterator{contract: _NodeManager.contract, event: "NodeActivated", logs: logs, sub: sub}, nil +} + +var NodeActivatedTopicHash = "0x49796be3ca168a59c8ae46c75a36a9bb3a84753d3e12a812f93ae010e783b14f" + +// WatchNodeActivated is a free log subscription operation binding the contract event 0x49796be3ca168a59c8ae46c75a36a9bb3a84753d3e12a812f93ae010e783b14f. +// +// Solidity: event NodeActivated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeActivated(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeActivated) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeActivated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeActivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeActivated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeActivated is a log parse operation binding the contract event 0x49796be3ca168a59c8ae46c75a36a9bb3a84753d3e12a812f93ae010e783b14f. +// +// Solidity: event NodeActivated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeActivated(log types.Log) (*NodeManagerNodeActivated, error) { + event := new(NodeManagerNodeActivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeActivated", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeApprovedIterator is returned from FilterNodeApproved and is used to iterate over the raw logs and unpacked data for NodeApproved events raised by the NodeManager contract. +type NodeManagerNodeApprovedIterator struct { + Event *NodeManagerNodeApproved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeApprovedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeApprovedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeApprovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeApproved represents a NodeApproved event raised by the NodeManager contract. +type NodeManagerNodeApproved struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeApproved is a free log retrieval operation binding the contract event 0x0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d. +// +// Solidity: event NodeApproved(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeApproved(opts *bind.FilterOpts) (*NodeManagerNodeApprovedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeApproved") + if err != nil { + return nil, err + } + return &NodeManagerNodeApprovedIterator{contract: _NodeManager.contract, event: "NodeApproved", logs: logs, sub: sub}, nil +} + +var NodeApprovedTopicHash = "0x0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d" + +// WatchNodeApproved is a free log subscription operation binding the contract event 0x0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d. +// +// Solidity: event NodeApproved(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeApproved(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeApproved) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeApproved") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeApproved) + if err := _NodeManager.contract.UnpackLog(event, "NodeApproved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeApproved is a log parse operation binding the contract event 0x0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d. +// +// Solidity: event NodeApproved(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeApproved(log types.Log) (*NodeManagerNodeApproved, error) { + event := new(NodeManagerNodeApproved) + if err := _NodeManager.contract.UnpackLog(event, "NodeApproved", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeBlacklistedIterator is returned from FilterNodeBlacklisted and is used to iterate over the raw logs and unpacked data for NodeBlacklisted events raised by the NodeManager contract. +type NodeManagerNodeBlacklistedIterator struct { + Event *NodeManagerNodeBlacklisted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeBlacklistedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeBlacklisted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeBlacklisted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeBlacklistedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeBlacklistedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeBlacklisted represents a NodeBlacklisted event raised by the NodeManager contract. +type NodeManagerNodeBlacklisted struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeBlacklisted is a free log retrieval operation binding the contract event 0x4714623279994517c446c8fb72c3fdaca26434da1e2490d3976fe0cd880cfa7a. +// +// Solidity: event NodeBlacklisted(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeBlacklisted(opts *bind.FilterOpts) (*NodeManagerNodeBlacklistedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeBlacklisted") + if err != nil { + return nil, err + } + return &NodeManagerNodeBlacklistedIterator{contract: _NodeManager.contract, event: "NodeBlacklisted", logs: logs, sub: sub}, nil +} + +var NodeBlacklistedTopicHash = "0x4714623279994517c446c8fb72c3fdaca26434da1e2490d3976fe0cd880cfa7a" + +// WatchNodeBlacklisted is a free log subscription operation binding the contract event 0x4714623279994517c446c8fb72c3fdaca26434da1e2490d3976fe0cd880cfa7a. +// +// Solidity: event NodeBlacklisted(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeBlacklisted(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeBlacklisted) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeBlacklisted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeBlacklisted) + if err := _NodeManager.contract.UnpackLog(event, "NodeBlacklisted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeBlacklisted is a log parse operation binding the contract event 0x4714623279994517c446c8fb72c3fdaca26434da1e2490d3976fe0cd880cfa7a. +// +// Solidity: event NodeBlacklisted(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeBlacklisted(log types.Log) (*NodeManagerNodeBlacklisted, error) { + event := new(NodeManagerNodeBlacklisted) + if err := _NodeManager.contract.UnpackLog(event, "NodeBlacklisted", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeDeactivatedIterator is returned from FilterNodeDeactivated and is used to iterate over the raw logs and unpacked data for NodeDeactivated events raised by the NodeManager contract. +type NodeManagerNodeDeactivatedIterator struct { + Event *NodeManagerNodeDeactivated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeDeactivatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeDeactivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeDeactivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeDeactivatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeDeactivatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeDeactivated represents a NodeDeactivated event raised by the NodeManager contract. +type NodeManagerNodeDeactivated struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeDeactivated is a free log retrieval operation binding the contract event 0xc6c3720fe673e87bb26e06be713d514278aa94c3939cfe7c64b9bea4d486824a. +// +// Solidity: event NodeDeactivated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeDeactivated(opts *bind.FilterOpts) (*NodeManagerNodeDeactivatedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeDeactivated") + if err != nil { + return nil, err + } + return &NodeManagerNodeDeactivatedIterator{contract: _NodeManager.contract, event: "NodeDeactivated", logs: logs, sub: sub}, nil +} + +var NodeDeactivatedTopicHash = "0xc6c3720fe673e87bb26e06be713d514278aa94c3939cfe7c64b9bea4d486824a" + +// WatchNodeDeactivated is a free log subscription operation binding the contract event 0xc6c3720fe673e87bb26e06be713d514278aa94c3939cfe7c64b9bea4d486824a. +// +// Solidity: event NodeDeactivated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeDeactivated(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeDeactivated) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeDeactivated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeDeactivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeDeactivated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeDeactivated is a log parse operation binding the contract event 0xc6c3720fe673e87bb26e06be713d514278aa94c3939cfe7c64b9bea4d486824a. +// +// Solidity: event NodeDeactivated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeDeactivated(log types.Log) (*NodeManagerNodeDeactivated, error) { + event := new(NodeManagerNodeDeactivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeDeactivated", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeProposedIterator is returned from FilterNodeProposed and is used to iterate over the raw logs and unpacked data for NodeProposed events raised by the NodeManager contract. +type NodeManagerNodeProposedIterator struct { + Event *NodeManagerNodeProposed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeProposedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeProposedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeProposedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeProposed represents a NodeProposed event raised by the NodeManager contract. +type NodeManagerNodeProposed struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeProposed is a free log retrieval operation binding the contract event 0xb1a7eec7dd1a516c3132d6d1f770758b19aa34c3a07c138caf662688b7e3556f. +// +// Solidity: event NodeProposed(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeProposed(opts *bind.FilterOpts) (*NodeManagerNodeProposedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeProposed") + if err != nil { + return nil, err + } + return &NodeManagerNodeProposedIterator{contract: _NodeManager.contract, event: "NodeProposed", logs: logs, sub: sub}, nil +} + +var NodeProposedTopicHash = "0xb1a7eec7dd1a516c3132d6d1f770758b19aa34c3a07c138caf662688b7e3556f" + +// WatchNodeProposed is a free log subscription operation binding the contract event 0xb1a7eec7dd1a516c3132d6d1f770758b19aa34c3a07c138caf662688b7e3556f. +// +// Solidity: event NodeProposed(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeProposed(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeProposed) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeProposed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeProposed) + if err := _NodeManager.contract.UnpackLog(event, "NodeProposed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeProposed is a log parse operation binding the contract event 0xb1a7eec7dd1a516c3132d6d1f770758b19aa34c3a07c138caf662688b7e3556f. +// +// Solidity: event NodeProposed(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeProposed(log types.Log) (*NodeManagerNodeProposed, error) { + event := new(NodeManagerNodeProposed) + if err := _NodeManager.contract.UnpackLog(event, "NodeProposed", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeRecoveryCompletedIterator is returned from FilterNodeRecoveryCompleted and is used to iterate over the raw logs and unpacked data for NodeRecoveryCompleted events raised by the NodeManager contract. +type NodeManagerNodeRecoveryCompletedIterator struct { + Event *NodeManagerNodeRecoveryCompleted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeRecoveryCompletedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryCompleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryCompleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeRecoveryCompletedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeRecoveryCompletedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeRecoveryCompleted represents a NodeRecoveryCompleted event raised by the NodeManager contract. +type NodeManagerNodeRecoveryCompleted struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeRecoveryCompleted is a free log retrieval operation binding the contract event 0x787d7bc525e7c4658c64e3e456d974a1be21cc196e8162a4bf1337a12cb38dac. +// +// Solidity: event NodeRecoveryCompleted(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeRecoveryCompleted(opts *bind.FilterOpts) (*NodeManagerNodeRecoveryCompletedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeRecoveryCompleted") + if err != nil { + return nil, err + } + return &NodeManagerNodeRecoveryCompletedIterator{contract: _NodeManager.contract, event: "NodeRecoveryCompleted", logs: logs, sub: sub}, nil +} + +var NodeRecoveryCompletedTopicHash = "0x787d7bc525e7c4658c64e3e456d974a1be21cc196e8162a4bf1337a12cb38dac" + +// WatchNodeRecoveryCompleted is a free log subscription operation binding the contract event 0x787d7bc525e7c4658c64e3e456d974a1be21cc196e8162a4bf1337a12cb38dac. +// +// Solidity: event NodeRecoveryCompleted(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeRecoveryCompleted(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeRecoveryCompleted) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeRecoveryCompleted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeRecoveryCompleted) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryCompleted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeRecoveryCompleted is a log parse operation binding the contract event 0x787d7bc525e7c4658c64e3e456d974a1be21cc196e8162a4bf1337a12cb38dac. +// +// Solidity: event NodeRecoveryCompleted(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeRecoveryCompleted(log types.Log) (*NodeManagerNodeRecoveryCompleted, error) { + event := new(NodeManagerNodeRecoveryCompleted) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryCompleted", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeRecoveryInitiatedIterator is returned from FilterNodeRecoveryInitiated and is used to iterate over the raw logs and unpacked data for NodeRecoveryInitiated events raised by the NodeManager contract. +type NodeManagerNodeRecoveryInitiatedIterator struct { + Event *NodeManagerNodeRecoveryInitiated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeRecoveryInitiatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeRecoveryInitiatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeRecoveryInitiatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeRecoveryInitiated represents a NodeRecoveryInitiated event raised by the NodeManager contract. +type NodeManagerNodeRecoveryInitiated struct { + EnodeId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeRecoveryInitiated is a free log retrieval operation binding the contract event 0xfd385c618a1e89d01fb9a21780846793e282e8bc0b60caf6ccb3e422d543fbfb. +// +// Solidity: event NodeRecoveryInitiated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeRecoveryInitiated(opts *bind.FilterOpts) (*NodeManagerNodeRecoveryInitiatedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeRecoveryInitiated") + if err != nil { + return nil, err + } + return &NodeManagerNodeRecoveryInitiatedIterator{contract: _NodeManager.contract, event: "NodeRecoveryInitiated", logs: logs, sub: sub}, nil +} + +var NodeRecoveryInitiatedTopicHash = "0xfd385c618a1e89d01fb9a21780846793e282e8bc0b60caf6ccb3e422d543fbfb" + +// WatchNodeRecoveryInitiated is a free log subscription operation binding the contract event 0xfd385c618a1e89d01fb9a21780846793e282e8bc0b60caf6ccb3e422d543fbfb. +// +// Solidity: event NodeRecoveryInitiated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeRecoveryInitiated(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeRecoveryInitiated) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeRecoveryInitiated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeRecoveryInitiated) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryInitiated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeRecoveryInitiated is a log parse operation binding the contract event 0xfd385c618a1e89d01fb9a21780846793e282e8bc0b60caf6ccb3e422d543fbfb. +// +// Solidity: event NodeRecoveryInitiated(string _enodeId, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeRecoveryInitiated(log types.Log) (*NodeManagerNodeRecoveryInitiated, error) { + event := new(NodeManagerNodeRecoveryInitiated) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryInitiated", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v1/bind/org.go b/permission/v1/bind/org.go new file mode 100644 index 0000000000..3e84b06990 --- /dev/null +++ b/permission/v1/bind/org.go @@ -0,0 +1,1075 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// OrgManagerABI is the input ABI used to generate the binding from. +const OrgManagerABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateOrg\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"approveOrgStatusUpdate\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getUltimateParent\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_pOrgId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addSubOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgIndex\",\"type\":\"uint256\"}],\"name\":\"getOrgInfo\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getSubOrgIndexes\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfOrgs\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_orgStatus\",\"type\":\"uint256\"}],\"name\":\"checkOrgStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_breadth\",\"type\":\"uint256\"},{\"name\":\"_depth\",\"type\":\"uint256\"}],\"name\":\"setUpOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"approveOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getOrgDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"checkOrgExists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"OrgApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"OrgPendingApproval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"}],\"name\":\"OrgSuspended\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"}],\"name\":\"OrgSuspensionRevoked\",\"type\":\"event\"}]" + +var OrgManagerParsedABI, _ = abi.JSON(strings.NewReader(OrgManagerABI)) + +// OrgManagerBin is the compiled bytecode used for deploying new contracts. +var OrgManagerBin = "0x608060405260018054600160a01b60ff021916905560046002819055600355600060065534801561002f57600080fd5b506040516020806138e98339810180604052602081101561004f57600080fd5b5051600180546001600160a01b0319166001600160a01b0390921691909117905561386a8061007f6000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c80637755ebdd1161008c578063e302831611610066578063e3028316146106c8578063f4d6d9f514610736578063f9953de5146107a4578063ffe40d1d14610812576100cf565b80637755ebdd146105925780638c8642df1461059a5780639e58eb9f14610654576100cf565b80630cc27493146100d457806314f775f914610154578063177c8d8a146101c45780631f953480146102a75780635c4f32ee146103655780635e99f6e5146104d4575b600080fd5b610142600480360360408110156100ea57600080fd5b810190602081018135600160201b81111561010457600080fd5b82018360208201111561011657600080fd5b803590602001918460018302840111600160201b8311171561013757600080fd5b9193509150356108b6565b60408051918252519081900360200190f35b6101c26004803603604081101561016a57600080fd5b810190602081018135600160201b81111561018457600080fd5b82018360208201111561019657600080fd5b803590602001918460018302840111600160201b831117156101b757600080fd5b919350915035610c48565b005b610232600480360360208110156101da57600080fd5b810190602081018135600160201b8111156101f457600080fd5b82018360208201111561020657600080fd5b803590602001918460018302840111600160201b8311171561022757600080fd5b509092509050610e3a565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561026c578181015183820152602001610254565b50505050905090810190601f1680156102995780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101c2600480360360408110156102bd57600080fd5b810190602081018135600160201b8111156102d757600080fd5b8201836020820111156102e957600080fd5b803590602001918460018302840111600160201b8311171561030a57600080fd5b919390929091602081019035600160201b81111561032757600080fd5b82018360208201111561033957600080fd5b803590602001918460018302840111600160201b8311171561035a57600080fd5b509092509050610fef565b6103826004803603602081101561037b57600080fd5b50356111cd565b60405180806020018060200180602001868152602001858152602001848103845289818151815260200191508051906020019080838360005b838110156103d35781810151838201526020016103bb565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b8381101561043357818101518382015260200161041b565b50505050905090810190601f1680156104605780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b8381101561049357818101518382015260200161047b565b50505050905090810190601f1680156104c05780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610542600480360360208110156104ea57600080fd5b810190602081018135600160201b81111561050457600080fd5b82018360208201111561051657600080fd5b803590602001918460018302840111600160201b8311171561053757600080fd5b509092509050611442565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561057e578181015183820152602001610566565b505050509050019250505060405180910390f35b61014261158f565b610640600480360360408110156105b057600080fd5b810190602081018135600160201b8111156105ca57600080fd5b8201836020820111156105dc57600080fd5b803590602001918460018302840111600160201b831117156105fd57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250611596915050565b604080519115158252519081900360200190f35b6101c26004803603606081101561066a57600080fd5b810190602081018135600160201b81111561068457600080fd5b82018360208201111561069657600080fd5b803590602001918460018302840111600160201b831117156106b757600080fd5b9193509150803590602001356116ee565b6101c2600480360360208110156106de57600080fd5b810190602081018135600160201b8111156106f857600080fd5b82018360208201111561070a57600080fd5b803590602001918460018302840111600160201b8311171561072b57600080fd5b50909250905061181a565b6103826004803603602081101561074c57600080fd5b810190602081018135600160201b81111561076657600080fd5b82018360208201111561077857600080fd5b803590602001918460018302840111600160201b8311171561079957600080fd5b509092509050611c25565b6101c2600480360360208110156107ba57600080fd5b810190602081018135600160201b8111156107d457600080fd5b8201836020820111156107e657600080fd5b803590602001918460018302840111600160201b8311171561080757600080fd5b509092509050611f8e565b6106406004803603602081101561082857600080fd5b810190602081018135600160201b81111561084257600080fd5b82018360208201111561085457600080fd5b803590602001918460018302840111600160201b8311171561087557600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061213b945050505050565b60015460408051600160e41b62e32cf902815290516000926001600160a01b031691630e32cf90916004808301926020929190829003018186803b1580156108fd57600080fd5b505afa158015610911573d6000803e3d6000fd5b505050506040513d602081101561092757600080fd5b50516001600160a01b0316331461097c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506109be925083915061213b9050565b1515600114610a0f5760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8260011480610a1e5750826002145b1515610a5e57604051600160e51b62461bcd0281526004018080602001828103825260258152602001806137986025913960400191505060405180910390fd5b6000610a9f86868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b9050600481815481101515610ab057fe5b9060005260206000209060080201600601546001141515610b0557604051600160e51b62461bcd0281526004018080602001828103825260278152602001806137bd6027913960400191505060405180910390fd5b6000808560011415610b1c57506002905080610b2d565b8560021415610b2d57506004905060035b610b6e88888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250869250611596915050565b1515600114610bb157604051600160e51b62461bcd0281526004018080602001828103825260278152602001806137e46027913960400191505060405180910390fd5b8560011415610bfe57610bf988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061225692505050565b610c3d565b610c3d88888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061251892505050565b979650505050505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610c9657600080fd5b505afa158015610caa573d6000803e3d6000fd5b505050506040513d6020811015610cc057600080fd5b50516001600160a01b03163314610d155760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610d57925083915061213b9050565b1515600114610da85760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8160011415610df557610df084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506126c592505050565b610e34565b610e3484848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061298392505050565b50505050565b60015460408051600160e41b62e32cf902815290516060926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610e8157600080fd5b505afa158015610e95573d6000803e3d6000fd5b505050506040513d6020811015610eab57600080fd5b50516001600160a01b03163314610f005760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6004610f4184848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b81548110610f4b57fe5b6000918252602091829020600460089092020101805460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815292830182828015610fe15780601f10610fb657610100808354040283529160200191610fe1565b820191906000526020600020905b815481529060010190602001808311610fc457829003601f168201915b505050505090505b92915050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561103d57600080fd5b505afa158015611051573d6000803e3d6000fd5b505050506040513d602081101561106757600080fd5b50516001600160a01b031633146110bc5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8383838360405160200180858580828437600160f91b601702920191825250600101838380828437808301925050509450505050506040516020818303038152906040526111098161213b565b1561114e5760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b6111c685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525060029250829150612a429050565b5050505050565b60608060606000806004868154811015156111e457fe5b906000526020600020906008020160000160048781548110151561120457fe5b906000526020600020906008020160020160048881548110151561122457fe5b906000526020600020906008020160040160048981548110151561124457fe5b90600052602060002090600802016006015460048a81548110151561126557fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561130c5780601f106112e15761010080835404028352916020019161130c565b820191906000526020600020905b8154815290600101906020018083116112ef57829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a508994509250840190508282801561139a5780601f1061136f5761010080835404028352916020019161139a565b820191906000526020600020905b81548152906001019060200180831161137d57829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156114285780601f106113fd57610100808354040283529160200191611428565b820191906000526020600020905b81548152906001019060200180831161140b57829003601f168201915b505050505092509450945094509450945091939590929450565b606061148383838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061213b92505050565b15156001146114d45760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b600061151584848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b905060048181548110151561152657fe5b906000526020600020906008020160070180548060200260200160405190810160405280929190818152602001828054801561158157602002820191906000526020600020905b81548152602001906001019080831161156d575b505050505091505092915050565b6004545b90565b600060056000846040516020018082805190602001908083835b602083106115cf5780518252601f1990920191602091820191016115b0565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001208152602001908152602001600020546000141561162957506000610fe9565b6000611634846121c9565b905060056000856040516020018082805190602001908083835b6020831061166d5780518252601f19909201916020918201910161164e565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001208152602001908152602001600020546000141580156116e65750826004828154811015156116d257fe5b906000526020600020906008020160010154145b949350505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561173c57600080fd5b505afa158015611750573d6000803e3d6000fd5b505050506040513d602081101561176657600080fd5b50516001600160a01b031633146117bb5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6118106040518060200160405280600081525085858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506001925060029150612a429050565b6002556003555050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561186857600080fd5b505afa15801561187c573d6000803e3d6000fd5b505050506040513d602081101561189257600080fd5b50516001600160a01b031633146118e75760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b61192982828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250611596915050565b151560011461197a5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b60006119bb83838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b905060026004828154811015156119ce57fe5b9060005260206000209060080201600101819055507fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c600482815481101515611a1357fe5b9060005260206000209060080201600001600483815481101515611a3357fe5b9060005260206000209060080201600201600484815481101515611a5357fe5b9060005260206000209060080201600401600485815481101515611a7357fe5b906000526020600020906008020160060154600260405180806020018060200180602001868152602001858152602001848103845289818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611b245780601f10611af957610100808354040283529160200191611b24565b820191906000526020600020905b815481529060010190602001808311611b0757829003601f168201915b5050848103835288546002600019610100600184161502019091160480825260209091019089908015611b985780601f10611b6d57610100808354040283529160200191611b98565b820191906000526020600020905b815481529060010190602001808311611b7b57829003601f168201915b5050848103825287546002600019610100600184161502019091160480825260209091019088908015611c0c5780601f10611be157610100808354040283529160200191611c0c565b820191906000526020600020905b815481529060010190602001808311611bef57829003601f168201915b50509850505050505050505060405180910390a1505050565b6060806060600080611c6c87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061213b92505050565b1515611cdb57868660008083838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820183528382528251908101909252918152949d509b50929950939750919550611f84945050505050565b6000611d1c88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b9050600481815481101515611d2d57fe5b9060005260206000209060080201600001600482815481101515611d4d57fe5b9060005260206000209060080201600201600483815481101515611d6d57fe5b9060005260206000209060080201600401600484815481101515611d8d57fe5b906000526020600020906008020160060154600485815481101515611dae57fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611e555780601f10611e2a57610100808354040283529160200191611e55565b820191906000526020600020905b815481529060010190602001808311611e3857829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a5089945092508401905082828015611ee35780601f10611eb857610100808354040283529160200191611ee3565b820191906000526020600020905b815481529060010190602001808311611ec657829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295995088945092508401905082828015611f715780601f10611f4657610100808354040283529160200191611f71565b820191906000526020600020905b815481529060010190602001808311611f5457829003601f168201915b5050505050925095509550955095509550505b9295509295909350565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611fdc57600080fd5b505afa158015611ff0573d6000803e3d6000fd5b505050506040513d602081101561200657600080fd5b50516001600160a01b0316331461205b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b81818080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061209d925083915061213b9050565b156120e25760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b6121366040518060200160405280600081525084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250829150612a429050565b505050565b600060056000836040516020018082805190602001908083835b602083106121745780518252601f199092019160209182019101612155565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014159050919050565b6000600160056000846040516020018082805190602001908083835b602083106122045780518252601f1990920191602091820191016121e5565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b612261816002611596565b15156001146122a457604051600160e51b62461bcd02815260040180806020018281038252603481526020018061380b6034913960400191505060405180910390fd5b60006122af826121c9565b905060036004828154811015156122c257fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b60048281548110151561230757fe5b906000526020600020906008020160000160048381548110151561232757fe5b906000526020600020906008020160020160048481548110151561234757fe5b906000526020600020906008020160040160048581548110151561236757fe5b9060005260206000209060080201600601546003604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156124185780601f106123ed57610100808354040283529160200191612418565b820191906000526020600020905b8154815290600101906020018083116123fb57829003601f168201915b505084810383528854600260001961010060018416150201909116048082526020909101908990801561248c5780601f106124615761010080835404028352916020019161248c565b820191906000526020600020905b81548152906001019060200180831161246f57829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156125005780601f106124d557610100808354040283529160200191612500565b820191906000526020600020905b8154815290600101906020018083116124e357829003601f168201915b50509850505050505050505060405180910390a15050565b612523816004611596565b151560011461257c5760408051600160e51b62461bcd02815260206004820152601a60248201527f6f7267206e6f7420696e2073757370656e646564207374617465000000000000604482015290519081900360640190fd5b6000612587826121c9565b9050600560048281548110151561259a57fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156125df57fe5b90600052602060002090600802016000016004838154811015156125ff57fe5b906000526020600020906008020160020160048481548110151561261f57fe5b906000526020600020906008020160040160048581548110151561263f57fe5b9060005260206000209060080201600601546005604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156124185780601f106123ed57610100808354040283529160200191612418565b6126d0816003611596565b15156001146127215760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b600061272c826121c9565b90506004808281548110151561273e57fe5b9060005260206000209060080201600101819055507f73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d9660048281548110151561278357fe5b90600052602060002090600802016000016004838154811015156127a357fe5b90600052602060002090600802016002016004848154811015156127c357fe5b90600052602060002090600802016004016004858154811015156127e357fe5b600091825260209182902060066008909202010154604080516060810183905260808082528754600260001961010060018416150201909116049082018190529293909283929183019183019060a0840190899080156128845780601f1061285957610100808354040283529160200191612884565b820191906000526020600020905b81548152906001019060200180831161286757829003601f168201915b50508481038352875460026000196101006001841615020190911604808252602090910190889080156128f85780601f106128cd576101008083540402835291602001916128f8565b820191906000526020600020905b8154815290600101906020018083116128db57829003601f168201915b505084810382528654600260001961010060018416150201909116048082526020909101908790801561296c5780601f106129415761010080835404028352916020019161296c565b820191906000526020600020905b81548152906001019060200180831161294f57829003601f168201915b505097505050505050505060405180910390a15050565b61298e816005611596565b15156001146129df5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b60006129ea826121c9565b905060026004828154811015156129fd57fe5b9060005260206000209060080201600101819055507f882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f60048281548110151561278357fe5b600080806001851415612ac457856040516020018082805190602001908083835b60208310612a825780518252601f199092019160209182019101612a63565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001209150612c03565b866040516020018082805190602001908083835b60208310612af75780518252601f199092019160209182019101612ad8565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120925086866040516020018083805190602001908083835b60208310612b685780518252601f199092019160209182019101612b49565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b60208310612bc45780518252601f199092019160209182019101612ba5565b6001836020036101000a038019825116818451168082178552505050505050905001925050506040516020818303038152906040528051906020012091505b600680546001908101918290556000848152600560205260408120929092556004805491612c339190830161355e565b90508560011415612cf85785600482815481101515612c4e57fe5b9060005260206000209060080201600601819055506000600482815481101515612c7457fe5b90600052602060002090600802016005018190555086600482815481101515612c9957fe5b90600052602060002090600802016003019080519060200190612cbd92919061358a565b5086600482815481101515612cce57fe5b90600052602060002090600802016004019080519060200190612cf292919061358a565b5061303c565b600084815260056020526040902054600354600480546000199093019450909184908110612d2257fe5b600091825260209091206007600890920201015410612d8b5760408051600160e51b62461bcd02815260206004820152601660248201527f62726561647468206c6576656c20657863656564656400000000000000000000604482015290519081900360640190fd5b6002546004805484908110612d9c57fe5b906000526020600020906008020160060154101515612e055760408051600160e51b62461bcd02815260206004820152601460248201527f6465707468206c6576656c206578636565646564000000000000000000000000604482015290519081900360640190fd5b6004805483908110612e1357fe5b906000526020600020906008020160060154600101600482815481101515612e3757fe5b90600052602060002090600802016006018190555081600482815481101515612e5c57fe5b60009182526020909120600560089092020101556004805483908110612e7e57fe5b9060005260206000209060080201600401600482815481101515612e9e57fe5b90600052602060002090600802016004019080546001816001161561010002031660029004612ece929190613608565b506000600483815481101515612ee057fe5b90600052602060002090600802016007018054809190600101612f03919061367d565b905081600484815481101515612f1557fe5b906000526020600020906008020160070182815481101515612f3357fe5b906000526020600020018190555088886040516020018083805190602001908083835b60208310612f755780518252601f199092019160209182019101612f56565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b60208310612fd15780518252601f199092019160209182019101612fb2565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405260048381548110151561301557fe5b9060005260206000209060080201600301908051906020019061303992919061358a565b50505b8660048281548110151561304c57fe5b9060005260206000209060080201600001908051906020019061307092919061358a565b508760048281548110151561308157fe5b906000526020600020906008020160020190805190602001906130a592919061358a565b50846004828154811015156130b657fe5b9060005260206000209060080201600101819055508460011415613316577f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b60048281548110151561310457fe5b906000526020600020906008020160000160048381548110151561312457fe5b906000526020600020906008020160020160048481548110151561314457fe5b906000526020600020906008020160040160048581548110151561316457fe5b9060005260206000209060080201600601546001604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156132155780601f106131ea57610100808354040283529160200191613215565b820191906000526020600020905b8154815290600101906020018083116131f857829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156132895780601f1061325e57610100808354040283529160200191613289565b820191906000526020600020905b81548152906001019060200180831161326c57829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156132fd5780601f106132d2576101008083540402835291602001916132fd565b820191906000526020600020905b8154815290600101906020018083116132e057829003601f168201915b50509850505050505050505060405180910390a1613554565b7fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c60048281548110151561334657fe5b906000526020600020906008020160000160048381548110151561336657fe5b906000526020600020906008020160020160048481548110151561338657fe5b90600052602060002090600802016004016004858154811015156133a657fe5b9060005260206000209060080201600601546002604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156134575780601f1061342c57610100808354040283529160200191613457565b820191906000526020600020905b81548152906001019060200180831161343a57829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156134cb5780601f106134a0576101008083540402835291602001916134cb565b820191906000526020600020905b8154815290600101906020018083116134ae57829003601f168201915b505084810382528754600260001961010060018416150201909116048082526020909101908890801561353f5780601f106135145761010080835404028352916020019161353f565b820191906000526020600020905b81548152906001019060200180831161352257829003601f168201915b50509850505050505050505060405180910390a15b5050505050505050565b8154818355818111156121365760080281600802836000526020600020918201910161213691906136a1565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106135cb57805160ff19168380011785556135f8565b828001600101855582156135f8579182015b828111156135f85782518255916020019190600101906135dd565b50613604929150613718565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061364157805485556135f8565b828001600101855582156135f857600052602060002091601f016020900482015b828111156135f8578254825591600101919060010190613662565b81548183558181111561213657600083815260209020612136918101908301613718565b61159391905b808211156136045760006136bb8282613732565b60018201600090556002820160006136d39190613732565b6136e1600383016000613732565b6136ef600483016000613732565b6005820160009055600682016000905560078201600061370f9190613779565b506008016136a7565b61159391905b80821115613604576000815560010161371e565b50805460018160011615610100020316600290046000825580601f106137585750613776565b601f0160209004906000526020600020908101906137769190613718565b50565b5080546000825590600052602060002090810190613776919061371856fe696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f7765646e6f742061206d6173746572206f72672e206f7065726174696f6e206e6f7420616c6c6f7765646f72672073746174757320646f6573206e6f7420616c6c6f7720746865206f7065726174696f6e6f7267206e6f7420696e20617070726f766564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e65a165627a7a72305820a443b9df85904ce8fcc8f004110f5465352bd5d4804a4e22547ef1fad4e8f21b0029" + +// DeployOrgManager deploys a new Ethereum contract, binding an instance of OrgManager to it. +func DeployOrgManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *OrgManager, error) { + parsed, err := abi.JSON(strings.NewReader(OrgManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OrgManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &OrgManager{OrgManagerCaller: OrgManagerCaller{contract: contract}, OrgManagerTransactor: OrgManagerTransactor{contract: contract}, OrgManagerFilterer: OrgManagerFilterer{contract: contract}}, nil +} + +// OrgManager is an auto generated Go binding around an Ethereum contract. +type OrgManager struct { + OrgManagerCaller // Read-only binding to the contract + OrgManagerTransactor // Write-only binding to the contract + OrgManagerFilterer // Log filterer for contract events +} + +// OrgManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type OrgManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OrgManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OrgManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OrgManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type OrgManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OrgManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OrgManagerSession struct { + Contract *OrgManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OrgManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OrgManagerCallerSession struct { + Contract *OrgManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OrgManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OrgManagerTransactorSession struct { + Contract *OrgManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OrgManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type OrgManagerRaw struct { + Contract *OrgManager // Generic contract binding to access the raw methods on +} + +// OrgManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OrgManagerCallerRaw struct { + Contract *OrgManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// OrgManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OrgManagerTransactorRaw struct { + Contract *OrgManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOrgManager creates a new instance of OrgManager, bound to a specific deployed contract. +func NewOrgManager(address common.Address, backend bind.ContractBackend) (*OrgManager, error) { + contract, err := bindOrgManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &OrgManager{OrgManagerCaller: OrgManagerCaller{contract: contract}, OrgManagerTransactor: OrgManagerTransactor{contract: contract}, OrgManagerFilterer: OrgManagerFilterer{contract: contract}}, nil +} + +// NewOrgManagerCaller creates a new read-only instance of OrgManager, bound to a specific deployed contract. +func NewOrgManagerCaller(address common.Address, caller bind.ContractCaller) (*OrgManagerCaller, error) { + contract, err := bindOrgManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &OrgManagerCaller{contract: contract}, nil +} + +// NewOrgManagerTransactor creates a new write-only instance of OrgManager, bound to a specific deployed contract. +func NewOrgManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*OrgManagerTransactor, error) { + contract, err := bindOrgManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &OrgManagerTransactor{contract: contract}, nil +} + +// NewOrgManagerFilterer creates a new log filterer instance of OrgManager, bound to a specific deployed contract. +func NewOrgManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*OrgManagerFilterer, error) { + contract, err := bindOrgManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &OrgManagerFilterer{contract: contract}, nil +} + +// bindOrgManager binds a generic wrapper to an already deployed contract. +func bindOrgManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OrgManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_OrgManager *OrgManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _OrgManager.Contract.OrgManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_OrgManager *OrgManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OrgManager.Contract.OrgManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_OrgManager *OrgManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _OrgManager.Contract.OrgManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_OrgManager *OrgManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _OrgManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_OrgManager *OrgManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OrgManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_OrgManager *OrgManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _OrgManager.Contract.contract.Transact(opts, method, params...) +} + +// CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. +// +// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerCaller) CheckOrgExists(opts *bind.CallOpts, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "checkOrgExists", _orgId) + return *ret0, err +} + +// CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. +// +// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerSession) CheckOrgExists(_orgId string) (bool, error) { + return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) +} + +// CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. +// +// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerCallerSession) CheckOrgExists(_orgId string) (bool, error) { + return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) +} + +// CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. +// +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +func (_OrgManager *OrgManagerCaller) CheckOrgStatus(opts *bind.CallOpts, _orgId string, _orgStatus *big.Int) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "checkOrgStatus", _orgId, _orgStatus) + return *ret0, err +} + +// CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. +// +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +func (_OrgManager *OrgManagerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { + return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) +} + +// CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. +// +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +func (_OrgManager *OrgManagerCallerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { + return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) +} + +// GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. +// +// Solidity: function getNumberOfOrgs() constant returns(uint256) +func (_OrgManager *OrgManagerCaller) GetNumberOfOrgs(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "getNumberOfOrgs") + return *ret0, err +} + +// GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. +// +// Solidity: function getNumberOfOrgs() constant returns(uint256) +func (_OrgManager *OrgManagerSession) GetNumberOfOrgs() (*big.Int, error) { + return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) +} + +// GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. +// +// Solidity: function getNumberOfOrgs() constant returns(uint256) +func (_OrgManager *OrgManagerCallerSession) GetNumberOfOrgs() (*big.Int, error) { + return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) +} + +// GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. +// +// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCaller) GetOrgDetails(opts *bind.CallOpts, _orgId string) (string, string, string, *big.Int, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _OrgManager.contract.Call(opts, out, "getOrgDetails", _orgId) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. +// +// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) +} + +// GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. +// +// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCallerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) +} + +// GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. +// +// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCaller) GetOrgInfo(opts *bind.CallOpts, _orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _OrgManager.contract.Call(opts, out, "getOrgInfo", _orgIndex) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. +// +// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) +} + +// GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. +// +// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCallerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) +} + +// GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. +// +// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +func (_OrgManager *OrgManagerCaller) GetSubOrgIndexes(opts *bind.CallOpts, _orgId string) ([]*big.Int, error) { + var ( + ret0 = new([]*big.Int) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "getSubOrgIndexes", _orgId) + return *ret0, err +} + +// GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. +// +// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +func (_OrgManager *OrgManagerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { + return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) +} + +// GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. +// +// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +func (_OrgManager *OrgManagerCallerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { + return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) +} + +// GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. +// +// Solidity: function getUltimateParent(string _orgId) constant returns(string) +func (_OrgManager *OrgManagerCaller) GetUltimateParent(opts *bind.CallOpts, _orgId string) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "getUltimateParent", _orgId) + return *ret0, err +} + +// GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. +// +// Solidity: function getUltimateParent(string _orgId) constant returns(string) +func (_OrgManager *OrgManagerSession) GetUltimateParent(_orgId string) (string, error) { + return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) +} + +// GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. +// +// Solidity: function getUltimateParent(string _orgId) constant returns(string) +func (_OrgManager *OrgManagerCallerSession) GetUltimateParent(_orgId string) (string, error) { + return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf9953de5. +// +// Solidity: function addOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactor) AddOrg(opts *bind.TransactOpts, _orgId string) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "addOrg", _orgId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf9953de5. +// +// Solidity: function addOrg(string _orgId) returns() +func (_OrgManager *OrgManagerSession) AddOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddOrg(&_OrgManager.TransactOpts, _orgId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf9953de5. +// +// Solidity: function addOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactorSession) AddOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddOrg(&_OrgManager.TransactOpts, _orgId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x1f953480. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId) returns() +func (_OrgManager *OrgManagerTransactor) AddSubOrg(opts *bind.TransactOpts, _pOrgId string, _orgId string) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "addSubOrg", _pOrgId, _orgId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x1f953480. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId) returns() +func (_OrgManager *OrgManagerSession) AddSubOrg(_pOrgId string, _orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddSubOrg(&_OrgManager.TransactOpts, _pOrgId, _orgId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x1f953480. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId) returns() +func (_OrgManager *OrgManagerTransactorSession) AddSubOrg(_pOrgId string, _orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddSubOrg(&_OrgManager.TransactOpts, _pOrgId, _orgId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xe3028316. +// +// Solidity: function approveOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactor) ApproveOrg(opts *bind.TransactOpts, _orgId string) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "approveOrg", _orgId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xe3028316. +// +// Solidity: function approveOrg(string _orgId) returns() +func (_OrgManager *OrgManagerSession) ApproveOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrg(&_OrgManager.TransactOpts, _orgId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xe3028316. +// +// Solidity: function approveOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactorSession) ApproveOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrg(&_OrgManager.TransactOpts, _orgId) +} + +// ApproveOrgStatusUpdate is a paid mutator transaction binding the contract method 0x14f775f9. +// +// Solidity: function approveOrgStatusUpdate(string _orgId, uint256 _action) returns() +func (_OrgManager *OrgManagerTransactor) ApproveOrgStatusUpdate(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "approveOrgStatusUpdate", _orgId, _action) +} + +// ApproveOrgStatusUpdate is a paid mutator transaction binding the contract method 0x14f775f9. +// +// Solidity: function approveOrgStatusUpdate(string _orgId, uint256 _action) returns() +func (_OrgManager *OrgManagerSession) ApproveOrgStatusUpdate(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrgStatusUpdate(&_OrgManager.TransactOpts, _orgId, _action) +} + +// ApproveOrgStatusUpdate is a paid mutator transaction binding the contract method 0x14f775f9. +// +// Solidity: function approveOrgStatusUpdate(string _orgId, uint256 _action) returns() +func (_OrgManager *OrgManagerTransactorSession) ApproveOrgStatusUpdate(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrgStatusUpdate(&_OrgManager.TransactOpts, _orgId, _action) +} + +// SetUpOrg is a paid mutator transaction binding the contract method 0x9e58eb9f. +// +// Solidity: function setUpOrg(string _orgId, uint256 _breadth, uint256 _depth) returns() +func (_OrgManager *OrgManagerTransactor) SetUpOrg(opts *bind.TransactOpts, _orgId string, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "setUpOrg", _orgId, _breadth, _depth) +} + +// SetUpOrg is a paid mutator transaction binding the contract method 0x9e58eb9f. +// +// Solidity: function setUpOrg(string _orgId, uint256 _breadth, uint256 _depth) returns() +func (_OrgManager *OrgManagerSession) SetUpOrg(_orgId string, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.SetUpOrg(&_OrgManager.TransactOpts, _orgId, _breadth, _depth) +} + +// SetUpOrg is a paid mutator transaction binding the contract method 0x9e58eb9f. +// +// Solidity: function setUpOrg(string _orgId, uint256 _breadth, uint256 _depth) returns() +func (_OrgManager *OrgManagerTransactorSession) SetUpOrg(_orgId string, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.SetUpOrg(&_OrgManager.TransactOpts, _orgId, _breadth, _depth) +} + +// UpdateOrg is a paid mutator transaction binding the contract method 0x0cc27493. +// +// Solidity: function updateOrg(string _orgId, uint256 _action) returns(uint256) +func (_OrgManager *OrgManagerTransactor) UpdateOrg(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "updateOrg", _orgId, _action) +} + +// UpdateOrg is a paid mutator transaction binding the contract method 0x0cc27493. +// +// Solidity: function updateOrg(string _orgId, uint256 _action) returns(uint256) +func (_OrgManager *OrgManagerSession) UpdateOrg(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.UpdateOrg(&_OrgManager.TransactOpts, _orgId, _action) +} + +// UpdateOrg is a paid mutator transaction binding the contract method 0x0cc27493. +// +// Solidity: function updateOrg(string _orgId, uint256 _action) returns(uint256) +func (_OrgManager *OrgManagerTransactorSession) UpdateOrg(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.UpdateOrg(&_OrgManager.TransactOpts, _orgId, _action) +} + +// OrgManagerOrgApprovedIterator is returned from FilterOrgApproved and is used to iterate over the raw logs and unpacked data for OrgApproved events raised by the OrgManager contract. +type OrgManagerOrgApprovedIterator struct { + Event *OrgManagerOrgApproved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgApprovedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgApprovedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgApprovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgApproved represents a OrgApproved event raised by the OrgManager contract. +type OrgManagerOrgApproved struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgApproved is a free log retrieval operation binding the contract event 0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c. +// +// Solidity: event OrgApproved(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) FilterOrgApproved(opts *bind.FilterOpts) (*OrgManagerOrgApprovedIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgApproved") + if err != nil { + return nil, err + } + return &OrgManagerOrgApprovedIterator{contract: _OrgManager.contract, event: "OrgApproved", logs: logs, sub: sub}, nil +} + +var OrgApprovedTopicHash = "0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c" + +// WatchOrgApproved is a free log subscription operation binding the contract event 0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c. +// +// Solidity: event OrgApproved(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) WatchOrgApproved(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgApproved) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgApproved") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgApproved) + if err := _OrgManager.contract.UnpackLog(event, "OrgApproved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgApproved is a log parse operation binding the contract event 0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c. +// +// Solidity: event OrgApproved(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) ParseOrgApproved(log types.Log) (*OrgManagerOrgApproved, error) { + event := new(OrgManagerOrgApproved) + if err := _OrgManager.contract.UnpackLog(event, "OrgApproved", log); err != nil { + return nil, err + } + return event, nil +} + +// OrgManagerOrgPendingApprovalIterator is returned from FilterOrgPendingApproval and is used to iterate over the raw logs and unpacked data for OrgPendingApproval events raised by the OrgManager contract. +type OrgManagerOrgPendingApprovalIterator struct { + Event *OrgManagerOrgPendingApproval // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgPendingApprovalIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgPendingApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgPendingApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgPendingApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgPendingApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgPendingApproval represents a OrgPendingApproval event raised by the OrgManager contract. +type OrgManagerOrgPendingApproval struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgPendingApproval is a free log retrieval operation binding the contract event 0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b. +// +// Solidity: event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) FilterOrgPendingApproval(opts *bind.FilterOpts) (*OrgManagerOrgPendingApprovalIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgPendingApproval") + if err != nil { + return nil, err + } + return &OrgManagerOrgPendingApprovalIterator{contract: _OrgManager.contract, event: "OrgPendingApproval", logs: logs, sub: sub}, nil +} + +var OrgPendingApprovalTopicHash = "0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b" + +// WatchOrgPendingApproval is a free log subscription operation binding the contract event 0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b. +// +// Solidity: event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) WatchOrgPendingApproval(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgPendingApproval) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgPendingApproval") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgPendingApproval) + if err := _OrgManager.contract.UnpackLog(event, "OrgPendingApproval", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgPendingApproval is a log parse operation binding the contract event 0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b. +// +// Solidity: event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) ParseOrgPendingApproval(log types.Log) (*OrgManagerOrgPendingApproval, error) { + event := new(OrgManagerOrgPendingApproval) + if err := _OrgManager.contract.UnpackLog(event, "OrgPendingApproval", log); err != nil { + return nil, err + } + return event, nil +} + +// OrgManagerOrgSuspendedIterator is returned from FilterOrgSuspended and is used to iterate over the raw logs and unpacked data for OrgSuspended events raised by the OrgManager contract. +type OrgManagerOrgSuspendedIterator struct { + Event *OrgManagerOrgSuspended // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgSuspendedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgSuspendedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgSuspendedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgSuspended represents a OrgSuspended event raised by the OrgManager contract. +type OrgManagerOrgSuspended struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgSuspended is a free log retrieval operation binding the contract event 0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96. +// +// Solidity: event OrgSuspended(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) FilterOrgSuspended(opts *bind.FilterOpts) (*OrgManagerOrgSuspendedIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgSuspended") + if err != nil { + return nil, err + } + return &OrgManagerOrgSuspendedIterator{contract: _OrgManager.contract, event: "OrgSuspended", logs: logs, sub: sub}, nil +} + +var OrgSuspendedTopicHash = "0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96" + +// WatchOrgSuspended is a free log subscription operation binding the contract event 0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96. +// +// Solidity: event OrgSuspended(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) WatchOrgSuspended(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgSuspended) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgSuspended") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgSuspended) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspended", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgSuspended is a log parse operation binding the contract event 0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96. +// +// Solidity: event OrgSuspended(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) ParseOrgSuspended(log types.Log) (*OrgManagerOrgSuspended, error) { + event := new(OrgManagerOrgSuspended) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspended", log); err != nil { + return nil, err + } + return event, nil +} + +// OrgManagerOrgSuspensionRevokedIterator is returned from FilterOrgSuspensionRevoked and is used to iterate over the raw logs and unpacked data for OrgSuspensionRevoked events raised by the OrgManager contract. +type OrgManagerOrgSuspensionRevokedIterator struct { + Event *OrgManagerOrgSuspensionRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgSuspensionRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspensionRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspensionRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgSuspensionRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgSuspensionRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgSuspensionRevoked represents a OrgSuspensionRevoked event raised by the OrgManager contract. +type OrgManagerOrgSuspensionRevoked struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgSuspensionRevoked is a free log retrieval operation binding the contract event 0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f. +// +// Solidity: event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) FilterOrgSuspensionRevoked(opts *bind.FilterOpts) (*OrgManagerOrgSuspensionRevokedIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgSuspensionRevoked") + if err != nil { + return nil, err + } + return &OrgManagerOrgSuspensionRevokedIterator{contract: _OrgManager.contract, event: "OrgSuspensionRevoked", logs: logs, sub: sub}, nil +} + +var OrgSuspensionRevokedTopicHash = "0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f" + +// WatchOrgSuspensionRevoked is a free log subscription operation binding the contract event 0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f. +// +// Solidity: event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) WatchOrgSuspensionRevoked(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgSuspensionRevoked) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgSuspensionRevoked") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgSuspensionRevoked) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspensionRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgSuspensionRevoked is a log parse operation binding the contract event 0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f. +// +// Solidity: event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) ParseOrgSuspensionRevoked(log types.Log) (*OrgManagerOrgSuspensionRevoked, error) { + event := new(OrgManagerOrgSuspensionRevoked) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspensionRevoked", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v1/bind/permission_impl.go b/permission/v1/bind/permission_impl.go new file mode 100644 index 0000000000..2f488be8f8 --- /dev/null +++ b/permission/v1/bind/permission_impl.go @@ -0,0 +1,983 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PermImplABI is the input ABI used to generate the binding from. +const PermImplABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"updateAccountStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_access\",\"type\":\"uint256\"},{\"name\":\"_voter\",\"type\":\"bool\"},{\"name\":\"_admin\",\"type\":\"bool\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addNewRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminOrg\",\"type\":\"string\"},{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"}],\"name\":\"setPolicy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"startBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"updateOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"}],\"name\":\"addAdminNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"assignAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updateNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"addAdminAccount\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"removeRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"validateAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"assignAccountRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"isOrgAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_breadth\",\"type\":\"uint256\"},{\"name\":\"_depth\",\"type\":\"uint256\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_pOrgId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addSubOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"startBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getPolicyDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"isNetworkAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"updateNodeStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getPendingOp\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminOrg\",\"type\":\"string\"},{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"},{\"name\":\"_networkBootStatus\",\"type\":\"bool\"}],\"name\":\"setMigrationPolicy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"},{\"name\":\"_orgManager\",\"type\":\"address\"},{\"name\":\"_rolesManager\",\"type\":\"address\"},{\"name\":\"_accountManager\",\"type\":\"address\"},{\"name\":\"_voterManager\",\"type\":\"address\"},{\"name\":\"_nodeManager\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_networkBootStatus\",\"type\":\"bool\"}],\"name\":\"PermissionsInitialized\",\"type\":\"event\"}]" + +var PermImplParsedABI, _ = abi.JSON(strings.NewReader(PermImplABI)) + +// PermImplBin is the compiled bytecode used for deploying new contracts. +var PermImplBin = "0x60806040526003600955600a805460ff191690553480156200002057600080fd5b5060405160c08062007181833981018060405260c08110156200004257600080fd5b508051602082015160408301516060840151608085015160a090950151600580546001600160a01b039687166001600160a01b03199182161790915560048054958716958216959095179094556001805493861693851693909317909255600080549185169184169190911790556002805494841694831694909417909355600380549290931691161790556170a380620000de6000396000f3fe608060405234801561001057600080fd5b50600436106101cf5760003560e01c8063655a8ef511610104578063b5546564116100a2578063dbfad71111610071578063dbfad71114611196578063f346a3a714611263578063f5ad584a146113cf578063f922f802146114df576101cf565b8063b554656414610ed8578063c3dc8e0914610f55578063cc9ba6fa1461101c578063d1aa0c2014611170576101cf565b80638baa8191116100de5780638baa819114610ba65780639bd3810114610cea578063a5843f0814610d9e578063a64d286014610dc1576101cf565b8063655a8ef5146109ac5780636b568d7614610a735780638884304114610b27576101cf565b8063404bf3eb116101715780634cbfa82e1161014b5780634cbfa82e146107f05780634fe57e7a146107f857806359a260a31461081e5780635ca5adbe146108e5576101cf565b8063404bf3eb1461068157806344478e79146107555780634b20f45f14610771576101cf565b80631c249912116101ad5780631c249912146104485780633bc07dea146104c75780633cf5f33b146105965780633f25c28814610613576101cf565b806304e81f1e146101d45780631b04c2761461025d5780631b6102201461033a575b600080fd5b61025b600480360360808110156101ea57600080fd5b810190602081018135600160201b81111561020457600080fd5b82018360208201111561021657600080fd5b803590602001918460018302840111600160201b8311171561023757600080fd5b91935091506001600160a01b038135811691602081013591604090910135166115ae565b005b61025b600480360360c081101561027357600080fd5b810190602081018135600160201b81111561028d57600080fd5b82018360208201111561029f57600080fd5b803590602001918460018302840111600160201b831117156102c057600080fd5b919390929091602081019035600160201b8111156102dd57600080fd5b8201836020820111156102ef57600080fd5b803590602001918460018302840111600160201b8311171561031057600080fd5b919350915080359060208101351515906040810135151590606001356001600160a01b0316611801565b61025b6004803603606081101561035057600080fd5b810190602081018135600160201b81111561036a57600080fd5b82018360208201111561037c57600080fd5b803590602001918460018302840111600160201b8311171561039d57600080fd5b919390929091602081019035600160201b8111156103ba57600080fd5b8201836020820111156103cc57600080fd5b803590602001918460018302840111600160201b831117156103ed57600080fd5b919390929091602081019035600160201b81111561040a57600080fd5b82018360208201111561041c57600080fd5b803590602001918460018302840111600160201b8311171561043d57600080fd5b509092509050611ac3565b61025b6004803603606081101561045e57600080fd5b810190602081018135600160201b81111561047857600080fd5b82018360208201111561048a57600080fd5b803590602001918460018302840111600160201b831117156104ab57600080fd5b91935091506001600160a01b0381358116916020013516611c02565b61025b600480360360808110156104dd57600080fd5b810190602081018135600160201b8111156104f757600080fd5b82018360208201111561050957600080fd5b803590602001918460018302840111600160201b8311171561052a57600080fd5b919390929091602081019035600160201b81111561054757600080fd5b82018360208201111561055957600080fd5b803590602001918460018302840111600160201b8311171561057a57600080fd5b91935091506001600160a01b0381358116916020013516611f01565b61025b600480360360608110156105ac57600080fd5b810190602081018135600160201b8111156105c657600080fd5b8201836020820111156105d857600080fd5b803590602001918460018302840111600160201b831117156105f957600080fd5b9193509150803590602001356001600160a01b0316612487565b61025b6004803603602081101561062957600080fd5b810190602081018135600160201b81111561064357600080fd5b82018360208201111561065557600080fd5b803590602001918460018302840111600160201b8311171561067657600080fd5b50909250905061278f565b61025b6004803603608081101561069757600080fd5b810190602081018135600160201b8111156106b157600080fd5b8201836020820111156106c357600080fd5b803590602001918460018302840111600160201b831117156106e457600080fd5b919390926001600160a01b0383351692604081019060200135600160201b81111561070e57600080fd5b82018360208201111561072057600080fd5b803590602001918460018302840111600160201b8311171561074157600080fd5b9193509150356001600160a01b03166129a3565b61075d612d62565b604080519115158252519081900360200190f35b61025b6004803603606081101561078757600080fd5b810190602081018135600160201b8111156107a157600080fd5b8201836020820111156107b357600080fd5b803590602001918460018302840111600160201b831117156107d457600080fd5b91935091506001600160a01b0381358116916020013516612ec0565b61075d613109565b61025b6004803603602081101561080e57600080fd5b50356001600160a01b0316613113565b61025b6004803603606081101561083457600080fd5b810190602081018135600160201b81111561084e57600080fd5b82018360208201111561086057600080fd5b803590602001918460018302840111600160201b8311171561088157600080fd5b919390929091602081019035600160201b81111561089e57600080fd5b8201836020820111156108b057600080fd5b803590602001918460018302840111600160201b831117156108d157600080fd5b9193509150356001600160a01b0316613434565b61025b600480360360608110156108fb57600080fd5b810190602081018135600160201b81111561091557600080fd5b82018360208201111561092757600080fd5b803590602001918460018302840111600160201b8311171561094857600080fd5b919390929091602081019035600160201b81111561096557600080fd5b82018360208201111561097757600080fd5b803590602001918460018302840111600160201b8311171561099857600080fd5b9193509150356001600160a01b03166136b0565b61025b600480360360608110156109c257600080fd5b810190602081018135600160201b8111156109dc57600080fd5b8201836020820111156109ee57600080fd5b803590602001918460018302840111600160201b83111715610a0f57600080fd5b919390929091602081019035600160201b811115610a2c57600080fd5b820183602082011115610a3e57600080fd5b803590602001918460018302840111600160201b83111715610a5f57600080fd5b9193509150356001600160a01b0316613b72565b61075d60048036036040811015610a8957600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610ab357600080fd5b820183602082011115610ac557600080fd5b803590602001918460018302840111600160201b83111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613dd6945050505050565b61025b60048036036060811015610b3d57600080fd5b810190602081018135600160201b811115610b5757600080fd5b820183602082011115610b6957600080fd5b803590602001918460018302840111600160201b83111715610b8a57600080fd5b91935091506001600160a01b0381358116916020013516613ec4565b61025b60048036036080811015610bbc57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610be657600080fd5b820183602082011115610bf857600080fd5b803590602001918460018302840111600160201b83111715610c1957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610c6b57600080fd5b820183602082011115610c7d57600080fd5b803590602001918460018302840111600160201b83111715610c9e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506143199050565b61075d60048036036040811015610d0057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610d2a57600080fd5b820183602082011115610d3c57600080fd5b803590602001918460018302840111600160201b83111715610d5d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550614827945050505050565b61025b60048036036040811015610db457600080fd5b5080359060200135614bdf565b61025b60048036036080811015610dd757600080fd5b810190602081018135600160201b811115610df157600080fd5b820183602082011115610e0357600080fd5b803590602001918460018302840111600160201b83111715610e2457600080fd5b919390929091602081019035600160201b811115610e4157600080fd5b820183602082011115610e5357600080fd5b803590602001918460018302840111600160201b83111715610e7457600080fd5b919390929091602081019035600160201b811115610e9157600080fd5b820183602082011115610ea357600080fd5b803590602001918460018302840111600160201b83111715610ec457600080fd5b9193509150356001600160a01b031661509a565b61025b60048036036060811015610eee57600080fd5b810190602081018135600160201b811115610f0857600080fd5b820183602082011115610f1a57600080fd5b803590602001918460018302840111600160201b83111715610f3b57600080fd5b9193509150803590602001356001600160a01b0316615468565b61025b60048036036060811015610f6b57600080fd5b810190602081018135600160201b811115610f8557600080fd5b820183602082011115610f9757600080fd5b803590602001918460018302840111600160201b83111715610fb857600080fd5b919390929091602081019035600160201b811115610fd557600080fd5b820183602082011115610fe757600080fd5b803590602001918460018302840111600160201b8311171561100857600080fd5b9193509150356001600160a01b03166157c1565b611024615add565b604080518215156060820152608080825286519082015285519091829160208084019284019160a08501918a019080838360005b83811015611070578181015183820152602001611058565b50505050905090810190601f16801561109d5780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156110d05781810151838201526020016110b8565b50505050905090810190601f1680156110fd5780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b83811015611130578181015183820152602001611118565b50505050905090810190601f16801561115d5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b61075d6004803603602081101561118657600080fd5b50356001600160a01b0316615cb0565b61025b600480360360808110156111ac57600080fd5b810190602081018135600160201b8111156111c657600080fd5b8201836020820111156111d857600080fd5b803590602001918460018302840111600160201b831117156111f957600080fd5b919390929091602081019035600160201b81111561121657600080fd5b82018360208201111561122857600080fd5b803590602001918460018302840111600160201b8311171561124957600080fd5b9193509150803590602001356001600160a01b0316615eb4565b6112d16004803603602081101561127957600080fd5b810190602081018135600160201b81111561129357600080fd5b8201836020820111156112a557600080fd5b803590602001918460018302840111600160201b831117156112c657600080fd5b50909250905061610c565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015611330578181015183820152602001611318565b50505050905090810190601f16801561135d5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015611390578181015183820152602001611378565b50505050905090810190601f1680156113bd5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61025b600480360360808110156113e557600080fd5b810190602081018135600160201b8111156113ff57600080fd5b82018360208201111561141157600080fd5b803590602001918460018302840111600160201b8311171561143257600080fd5b919390929091602081019035600160201b81111561144f57600080fd5b82018360208201111561146157600080fd5b803590602001918460018302840111600160201b8311171561148257600080fd5b919390929091602081019035600160201b81111561149f57600080fd5b8201836020820111156114b157600080fd5b803590602001918460018302840111600160201b831117156114d257600080fd5b9193509150351515616274565b61025b600480360360808110156114f557600080fd5b810190602081018135600160201b81111561150f57600080fd5b82018360208201111561152157600080fd5b803590602001918460018302840111600160201b8311171561154257600080fd5b919390929091602081019035600160201b81111561155f57600080fd5b82018360208201111561157157600080fd5b803590602001918460018302840111600160201b8311171561159257600080fd5b91935091506001600160a01b0381358116916020013516616363565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156115fc57600080fd5b505afa158015611610573d6000803e3d6000fd5b505050506040513d602081101561162657600080fd5b50516001600160a01b0316331461167157604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8085858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506116b59250849150839050614827565b15156001146116f857604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b83600114806117075750836002145b806117125750836003145b151561175257604051600160e51b62461bcd028152600401808060200182810382526025815260200180616fe36025913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03878116602483015260448201879052606060048301908152606483018a90529216916384b7a84a918a918a918a918a918190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117e057600080fd5b505af11580156117f4573d6000803e3d6000fd5b5050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561184f57600080fd5b505afa158015611863573d6000803e3d6000fd5b505050506040513d602081101561187957600080fd5b50516001600160a01b031633146118c457604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611906925083915061690c9050565b151560011461194d5760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b8187878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506119919250849150839050614827565b15156001146119d457604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b600154604051600160e01b637b713579028152604481018990528715156064820152861515608482015260a06004820190815260a482018d90526001600160a01b0390921691637b713579918e918e918e918e918e918e918e91908190602481019060c4018a8a80828437600083820152601f01601f191690910184810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b158015611a9e57600080fd5b505af1158015611ab2573d6000803e3d6000fd5b505050505050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611b1157600080fd5b505afa158015611b25573d6000803e3d6000fd5b505050506040513d6020811015611b3b57600080fd5b50516001600160a01b03163314611b8657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff1615611bd25760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b611bde60068888616f14565b50611beb60078686616f14565b50611bf860088484616f14565b5050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611c5057600080fd5b505afa158015611c64573d6000803e3d6000fd5b505050506040513d6020811015611c7a57600080fd5b50516001600160a01b03163314611cc557604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80611ccf81615cb0565b1515600114611d1257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03858116602483015260046044830181905260608382019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611da457600080fd5b505af1158015611db8573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b03888116606483015260066084830181905260a060048401908152815460001960018216156101000201169590950460a4840181905291909316955063e98ac22d945091928a928a928a928692909182916024820191604481019160c49091019086908015611e855780601f10611e5a57610100808354040283529160200191611e85565b820191906000526020600020905b815481529060010190602001808311611e6857829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015611ee257600080fd5b505af1158015611ef6573d6000803e3d6000fd5b505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611f4f57600080fd5b505afa158015611f63573d6000803e3d6000fd5b505050506040513d6020811015611f7957600080fd5b50516001600160a01b03163314611fc457604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80611fce81615cb0565b151560011461201157604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b61205387878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506169fb915050565b15156001146120ac5760408051600160e51b62461bcd02815260206004820152601260248201527f4e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261214693909290918301828280156121395780601f1061210e57610100808354040283529160200191612139565b820191906000526020600020905b81548152906001019060200180831161211c57829003601f168201915b5050505050836001616a68565b1561247e5760048054604051600160e11b637181418b0281526020928101928352602481018990526001600160a01b039091169163e3028316918a918a918190604401848480828437600081840152601f19601f8201169050808301925050509350505050600060405180830381600087803b1580156121c557600080fd5b505af11580156121d9573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526008805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b7135799650948e948e94939192839290918291602481019160c4909101908a9080156122a95780601f1061227e576101008083540402835291602001916122a9565b820191906000526020600020905b81548152906001019060200180831161228c57829003601f168201915b50508381038252878152602001888880828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156122f757600080fd5b505af115801561230b573d6000803e3d6000fd5b505060035460408051600160e11b63435e1b2902815260048101918252604481018990526001600160a01b0390921693506386bc36529250889188918c918c919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156123b857600080fd5b505af11580156123cc573d6000803e3d6000fd5b505060005460408051600160e01b63c214e5e50281526001600160a01b03888116602483015260048201928352604482018c9052909216935063c214e5e592508a918a9188918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b15801561245457600080fd5b505af1158015612468573d6000803e3d6000fd5b505050506040513d6020811015611ef657600080fd5b50505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156124d557600080fd5b505afa1580156124e9573d6000803e3d6000fd5b505050506040513d60208110156124ff57600080fd5b50516001600160a01b0316331461254a57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8061255481615cb0565b151560011461259757604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b6004805460408051600160e01b630cc2749302815260248101879052928301908152604483018790526000926001600160a01b0390921691630cc27493918991899189918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b15801561261d57600080fd5b505af1158015612631573d6000803e3d6000fd5b505050506040513d602081101561264757600080fd5b505160028054604051600160e01b63e98ac22d0281526000606482018190526084820185905260a0600483019081526006805460001960018216156101000201169590950460a484018190529596506001600160a01b039093169463e98ac22d94938c938c939289929182916024820191604481019160c4909101908a9080156127125780601f106126e757610100808354040283529160200191612712565b820191906000526020600020905b8154815290600101906020018083116126f557829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b15801561276f57600080fd5b505af1158015612783573d6000803e3d6000fd5b50505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156127dd57600080fd5b505afa1580156127f1573d6000803e3d6000fd5b505050506040513d602081101561280757600080fd5b50516001600160a01b0316331461285257604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff161561289e5760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b60035460408051600160e21b6338ec276102815260048101918252604481018590526001600160a01b039092169163e3b09d84918691869160069181906024810190606401868680828437600083820152601f01601f19169091018481038352855460026000196101006001841615020190911604808252602090910191508590801561296c5780601f106129415761010080835404028352916020019161296c565b820191906000526020600020905b81548152906001019060200180831161294f57829003601f168201915b505095505050505050600060405180830381600087803b15801561298f57600080fd5b505af115801561247e573d6000803e3d6000fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156129f157600080fd5b505afa158015612a05573d6000803e3d6000fd5b505050506040513d6020811015612a1b57600080fd5b50516001600160a01b03163314612a6657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612aa89250839150616b629050565b1515600114612af95760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b81612b0381615cb0565b1515600114612b4657604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b600054604051600160e01b63e3483a9d0281526001600160a01b0388811660048301908152600160648401819052608060248501908152608485018d9052929094169363e3483a9d938b938e938e938d938d9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b158015612c0957600080fd5b505af1158015612c1d573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b038b8116606483015260046084830181905260a08382019081526006805460001960018216156101000201169690960460a4850181905292909416965063e98ac22d95508e938e938e9382916024810191604482019160c401908a908015612ce35780601f10612cb857610100808354040283529160200191612ce3565b820191906000526020600020905b815481529060010190602001808311612cc657829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015612d4057600080fd5b505af1158015612d54573d6000803e3d6000fd5b505050505050505050505050565b60055460408051600160e21b63395c945702815290516000926001600160a01b03169163e572515c916004808301926020929190829003018186803b158015612daa57600080fd5b505afa158015612dbe573d6000803e3d6000fd5b505050506040513d6020811015612dd457600080fd5b50516001600160a01b03163314612e1f57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff1615612e6b5760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b600a805460ff1916600117908190556040805160ff9290921615158252517f04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf9181900360200190a1600a5460ff1691505b5090565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f0e57600080fd5b505afa158015612f22573d6000803e3d6000fd5b505050506040513d6020811015612f3857600080fd5b50516001600160a01b03163314612f8357604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80612f8d81615cb0565b1515600114612fd057604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261306a939092909183018282801561305d5780601f106130325761010080835404028352916020019161305d565b820191906000526020600020905b81548152906001019060200180831161304057829003601f168201915b5050505050836006616a68565b1561310257600054604051600160e11b63425bd4250281526001600160a01b0385811660248301526005604483018190526060600484019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611ee257600080fd5b5050505050565b600a5460ff165b90565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561316157600080fd5b505afa158015613175573d6000803e3d6000fd5b505050506040513d602081101561318b57600080fd5b50516001600160a01b031633146131d657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff16156132225760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526132bc93909290918301828280156132af5780601f10613284576101008083540402835291602001916132af565b820191906000526020600020905b81548152906001019060200180831161329257829003601f168201915b5050505050836001616c27565b600054604051600160e01b63e3483a9d0281526001600160a01b038481166004830190815260026064840181905260806024850190815260068054600019600182161561010002011683900460848701819052949096169563e3483a9d95899591946007949390929091604481019160a490910190879080156133805780601f1061335557610100808354040283529160200191613380565b820191906000526020600020905b81548152906001019060200180831161336357829003601f168201915b50508381038252855460026000196101006001841615020190911604808252602090910190869080156133f45780601f106133c9576101008083540402835291602001916133f4565b820191906000526020600020905b8154815290600101906020018083116133d757829003601f168201915b50509650505050505050600060405180830381600087803b15801561341857600080fd5b505af115801561342c573d6000803e3d6000fd5b505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561348257600080fd5b505afa158015613496573d6000803e3d6000fd5b505050506040513d60208110156134ac57600080fd5b50516001600160a01b031633146134f757604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b84848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613539925083915061690c9050565b15156001146135805760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b8186868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506135c49250849150839050614827565b151560011461360757604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b60035460408051600160e01b633f5e1a4502815260048101918252604481018890526001600160a01b0390921691633f5e1a4591899189918d918d919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612d4057600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156136fe57600080fd5b505afa158015613712573d6000803e3d6000fd5b505050506040513d602081101561372857600080fd5b50516001600160a01b0316331461377357604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506137b5925083915061690c9050565b15156001146137fc5760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b8184848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506138409250849150839050614827565b151560011461388357604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b60408051602080820190815260078054600260001961010060018416150201909116049383018490529290918291606090910190849080156139065780601f106138db57610100808354040283529160200191613906565b820191906000526020600020905b8154815290600101906020018083116138e957829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014158015613a7357506040805160208082019081526008805460026000196101006001841615020190911604938301849052929091829160609091019084908015613a015780601f106139d657610100808354040283529160200191613a01565b820191906000526020600020905b8154815290600101906020018083116139e457829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014155b1515613ac95760408051600160e51b62461bcd02815260206004820152601d60248201527f61646d696e20726f6c65732063616e6e6f742062652072656d6f766564000000604482015290519081900360640190fd5b60015460408051600160e11b63531a180902815260048101918252604481018a90526001600160a01b039092169163a6343012918b918b918b918b919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612d4057600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015613bc057600080fd5b505afa158015613bd4573d6000803e3d6000fd5b505050506040513d6020811015613bea57600080fd5b50516001600160a01b03163314613c3557604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80613c3f81615cb0565b1515600114613c8257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152613d1c9390929091830182828015613d0f5780601f10613ce457610100808354040283529160200191613d0f565b820191906000526020600020905b815481529060010190602001808311613cf257829003601f168201915b5050505050836005616a68565b1561342c57600354604051600160e11b63066280a3028152600560448201819052606060048301908152606483018790526001600160a01b0390931692630cc5014692889288928c928c92919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b15801561276f57600080fd5b6000805460408051600160e11b6335ab46bb0281526001600160a01b0386811660048301908152602483019384528651604484015286519190941693636b568d76938893889360649091019060208501908083838c5b83811015613e44578181015183820152602001613e2c565b50505050905090810190601f168015613e715780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015613e8f57600080fd5b505afa158015613ea3573d6000803e3d6000fd5b505050506040513d6020811015613eb957600080fd5b505190505b92915050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015613f1257600080fd5b505afa158015613f26573d6000803e3d6000fd5b505050506040513d6020811015613f3c57600080fd5b50516001600160a01b03163314613f8757604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80613f9181615cb0565b1515600114613fd457604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261406e93909290918301828280156140615780601f1061403657610100808354040283529160200191614061565b820191906000526020600020905b81548152906001019060200180831161404457829003601f168201915b5050505050836004616a68565b156131025760008054604051600160e01b631d09dc930281526020600482019081526024820188905283926001600160a01b031691631d09dc93918a918a91908190604401848480828437600081840152601f19601f82011690508083019250505093505050506040805180830381600087803b1580156140ee57600080fd5b505af1158015614102573d6000803e3d6000fd5b505050506040513d604081101561411857600080fd5b508051602090910151909250905081156141c65760068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526141c693909290918301828280156141b95780601f1061418e576101008083540402835291602001916141b9565b820191906000526020600020905b81548152906001019060200180831161419c57829003601f168201915b5050505050826000616c27565b6000805460408051600160e01b63c214e5e50281526001600160a01b03898116602483015260048201928352604482018b90529092169163c214e5e5918b918b918b918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b15801561424b57600080fd5b505af115801561425f573d6000803e3d6000fd5b505050506040513d602081101561427557600080fd5b505190508015611bf85760068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152611bf8939092909183018282801561430c5780601f106142e15761010080835404028352916020019161430c565b820191906000526020600020905b8154815290600101906020018083116142ef57829003601f168201915b5050505050876001616c27565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561436757600080fd5b505afa15801561437b573d6000803e3d6000fd5b505050506040513d602081101561439157600080fd5b50516001600160a01b031633146143dc57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80836143e88282614827565b151560011461442b57604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b846144358161690c565b151560011461447c5760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b6144868787613dd6565b15156001146144df5760408051600160e51b62461bcd02815260206004820152601d60248201527f6f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b6144e98587616dbd565b15156001146145425760408051600160e51b62461bcd02815260206004820152601460248201527f726f6c6520646f6573206e6f7420657869737473000000000000000000000000604482015290519081900360640190fd5b6001546000906001600160a01b031663be322e54878961456181616dd8565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b838110156145b557818101518382015260200161459d565b50505050905090810190601f1680156145e25780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b838110156146155781810151838201526020016145fd565b50505050905090810190601f1680156146425780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b8381101561467557818101518382015260200161465d565b50505050905090810190601f1680156146a25780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b1580156146c357600080fd5b505afa1580156146d7573d6000803e3d6000fd5b505050506040513d60208110156146ed57600080fd5b505160008054604051600160e21b63050e95810281526001600160a01b038c81166004830190815285151560648401526080602484019081528d5160848501528d51969750919093169463143a5604948e948e948e948a9492939092604483019260a401916020890191908190849084905b8381101561477757818101518382015260200161475f565b50505050905090810190601f1680156147a45780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838360005b838110156147d75781810151838201526020016147bf565b50505050905090810190601f1680156148045780820380516001836020036101000a031916815260200191505b509650505050505050600060405180830381600087803b158015612d4057600080fd5b600080546001600160a01b031663e8b42bf4848461484481616dd8565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b031681526020018060200180602001838103835285818151815260200191508051906020019080838360005b838110156148ac578181015183820152602001614894565b50505050905090810190601f1680156148d95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561490c5781810151838201526020016148f4565b50505050905090810190601f1680156149395780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038186803b15801561495957600080fd5b505afa15801561496d573d6000803e3d6000fd5b505050506040513d602081101561498357600080fd5b50511561499257506001613ebe565b6001546000805460408051600160e01b6381d66b230281526001600160a01b03888116600483015291519482169463be322e549493909216926381d66b2392602480840193829003018186803b1580156149eb57600080fd5b505afa1580156149ff573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015614a2857600080fd5b810190808051600160201b811115614a3f57600080fd5b82016020810184811115614a5257600080fd5b8151600160201b811182820187101715614a6b57600080fd5b505092919050505084614a7d86616dd8565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b83811015614ad1578181015183820152602001614ab9565b50505050905090810190601f168015614afe5780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b83811015614b31578181015183820152602001614b19565b50505050905090810190601f168015614b5e5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015614b91578181015183820152602001614b79565b50505050905090810190601f168015614bbe5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b158015613e8f57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015614c2d57600080fd5b505afa158015614c41573d6000803e3d6000fd5b505050506040513d6020811015614c5757600080fd5b50516001600160a01b03163314614ca257604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff1615614cee5760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b60048054604051600160e01b639e58eb9f028152602481018690526044810185905260609281019283526006805460026000196001831615610100020190911604606483018190526001600160a01b0390931693639e58eb9f9391928892889291829160849091019086908015614da65780601f10614d7b57610100808354040283529160200191614da6565b820191906000526020600020905b815481529060010190602001808311614d8957829003601f168201915b5050945050505050600060405180830381600087803b158015614dc857600080fd5b505af1158015614ddc573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526007805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b71357996509460069490928392918291602481019160c49091019089908015614ea95780601f10614e7e57610100808354040283529160200191614ea9565b820191906000526020600020905b815481529060010190602001808311614e8c57829003601f168201915b5050838103825287546002600019610100600184161502019091160480825260209091019088908015614f1d5780601f10614ef257610100808354040283529160200191614f1d565b820191906000526020600020905b815481529060010190602001808311614f0057829003601f168201915b5050975050505050505050600060405180830381600087803b158015614f4257600080fd5b505af1158015614f56573d6000803e3d6000fd5b505060005460408051600160e01b63cef7f6af028152600481019182526007805460026000196001831615610100020190911604604483018190526001600160a01b03909416955063cef7f6af945092600892918291602482019160640190869080156150045780601f10614fd957610100808354040283529160200191615004565b820191906000526020600020905b815481529060010190602001808311614fe757829003601f168201915b50508381038252845460026000196101006001841615020190911604808252602090910190859080156150785780601f1061504d57610100808354040283529160200191615078565b820191906000526020600020905b81548152906001019060200180831161505b57829003601f168201915b5050945050505050600060405180830381600087803b15801561298f57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156150e857600080fd5b505afa1580156150fc573d6000803e3d6000fd5b505050506040513d602081101561511257600080fd5b50516001600160a01b0316331461515d57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b86868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061519f9250839150616b629050565b15156001146151f05760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8188888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506152349250849150839050614827565b151560011461527757604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b6004805460408051600160e71b623f2a69028152928301908152604483018c90526001600160a01b0390911691631f953480918d918d918d918d919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b15801561531f57600080fd5b505af1158015615333573d6000803e3d6000fd5b5050505060608a8a8a8a60405160200180858580828437600160f91b6017029201918252506001018383808284376040805191909301818103601f1901825290925250955050891593506117f4925050505760035460408051600160e01b633f5e1a4502815260048101918252604481018990526001600160a01b0390921691633f5e1a45918a918a918691819060248101906064018686808284376000838201819052601f909101601f191690920185810384528651815286516020918201939188019250908190849084905b83811015615419578181015183820152602001615401565b50505050905090810190601f1680156154465780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015611a9e57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156154b657600080fd5b505afa1580156154ca573d6000803e3d6000fd5b505050506040513d60208110156154e057600080fd5b50516001600160a01b0316331461552b57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8061553581615cb0565b151560011461557857604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b82600114806155875750826002145b15156155dd5760408051600160e51b62461bcd02815260206004820152601560248201527f4f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b60008084600114156155f55750600290506003615606565b846002141561560657506003905060055b61564787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508592506169fb915050565b15156001146156a05760408051600160e51b62461bcd02815260206004820152601560248201527f6f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152615739939092909183018282801561572d5780601f106157025761010080835404028352916020019161572d565b820191906000526020600020905b81548152906001019060200180831161571057829003601f168201915b50505050508584616a68565b1561247e576004805460408051600160e01b6314f775f902815260248101899052928301908152604483018990526001600160a01b03909116916314f775f9918a918a918a918190606401858580828437600081840152601f19601f820116905080830192505050945050505050600060405180830381600087803b1580156117e057600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561580f57600080fd5b505afa158015615823573d6000803e3d6000fd5b505050506040513d602081101561583957600080fd5b50516001600160a01b0316331461588457604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8061588e81615cb0565b15156001146158d157604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b600354604051600160e11b63066280a30281526004604482018190526060828201908152606483018790526001600160a01b0390931692630cc5014692889288928c928c92919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b15801561598557600080fd5b505af1158015615999573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d02815260006064820181905260056084830181905260a0600484019081526006805460001960018216156101000201169690960460a485018190526001600160a01b03909516975063e98ac22d96508d948d948d948d9490939092909182916024820191604481019160c4909101908c908015615a6a5780601f10615a3f57610100808354040283529160200191615a6a565b820191906000526020600020905b815481529060010190602001808311615a4d57829003601f168201915b505084810383528981526020018a8a80828437600083820152601f01601f191690910185810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b15801561276f57600080fd5b600a5460068054604080516020601f6002600019600187161561010002019095169490940493840181900481028201810190925282815260609485948594600094919360079360089360ff909116928691830182828015615b7f5780601f10615b5457610100808354040283529160200191615b7f565b820191906000526020600020905b815481529060010190602001808311615b6257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295995088945092508401905082828015615c0d5780601f10615be257610100808354040283529160200191615c0d565b820191906000526020600020905b815481529060010190602001808311615bf057829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295985087945092508401905082828015615c9b5780601f10615c7057610100808354040283529160200191615c9b565b820191906000526020600020905b815481529060010190602001808311615c7e57829003601f168201915b50505050509150935093509350935090919293565b60408051602080820190815260078054600260001961010060018416150201909116049383018490526000939092829160609091019084908015615d355780601f10615d0a57610100808354040283529160200191615d35565b820191906000526020600020905b815481529060010190602001808311615d1857829003601f168201915b505060408051601f19818403018152828252805160209091012060008054600160e01b6381d66b230285526001600160a01b038a8116600487015293519297509290921694506381d66b239350602480840193829003018186803b158015615d9c57600080fd5b505afa158015615db0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015615dd957600080fd5b810190808051600160201b811115615df057600080fd5b82016020810184811115615e0357600080fd5b8151600160201b811182820187101715615e1c57600080fd5b50509291905050506040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015615e64578181015183820152602001615e4c565b50505050905090810190601f168015615e915780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120149050919050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015615f0257600080fd5b505afa158015615f16573d6000803e3d6000fd5b505050506040513d6020811015615f2c57600080fd5b50516001600160a01b03163314615f7757604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8086868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250615fbb9250849150839050614827565b1515600114615ffe57604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b836001148061600d5750836002145b806160185750836003145b151561605857604051600160e51b62461bcd028152600401808060200182810382526025815260200180616fe36025913960400191505060405180910390fd5b600354604051600160e11b63066280a302815260448101869052606060048201908152606482018890526001600160a01b0390921691630cc5014691899189918d918d918b919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015612d4057600080fd5b600254604051600160e21b62539ab302815260206004820190815260248201849052606092839260009283926001600160a01b03169163014e6acc9189918991908190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561618c57600080fd5b505afa1580156161a0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156161c957600080fd5b810190808051600160201b8111156161e057600080fd5b820160208101848111156161f357600080fd5b8151600160201b81118282018710171561620c57600080fd5b50509291906020018051600160201b81111561622757600080fd5b8201602081018481111561623a57600080fd5b8151600160201b81118282018710171561625357600080fd5b50506020820151604090920151949b909a5090985092965091945050505050565b6005546001600160a01b031633146162d65760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600a5460009060ff16156163225760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b61632e60068989616f14565b5061633b60078787616f14565b5061634860088585616f14565b5050600a805460ff1916911515919091179055505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156163b157600080fd5b505afa1580156163c5573d6000803e3d6000fd5b505050506040513d60208110156163db57600080fd5b50516001600160a01b0316331461642657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460019060ff16151581146164755760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b8161647f81615cb0565b15156001146164c257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60028054604051600160e01b63e98ac22d0281526001600160a01b03878116606483015260016084830181905260a06004840190815260068054600019818516156101000201169690960460a48501819052929094169463e98ac22d9490938e938e938e938e938e9382916024810191604482019160c401908c90801561658a5780601f1061655f5761010080835404028352916020019161658a565b820191906000526020600020905b81548152906001019060200180831161656d57829003601f168201915b505084810383528981526020018a8a80828437600083820152601f01601f191690910185810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b1580156165fd57600080fd5b505af1158015616611573d6000803e3d6000fd5b505060048054604051600160e01b63f9953de50281526020928101928352602481018c90526001600160a01b03909116935063f9953de592508b918b918190604401848480828437600081840152601f19601f8201169050808301925050509350505050600060405180830381600087803b15801561668f57600080fd5b505af11580156166a3573d6000803e3d6000fd5b505060035460408051600160e11b6354bd220302815260048101918252604481018a90526001600160a01b03909216935063a97a44069250899189918d918d919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b15801561675057600080fd5b505af1158015616764573d6000803e3d6000fd5b505050506167a88489898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613dd692505050565b15156001146168015760408051600160e51b62461bcd02815260206004820152601d60248201527f4f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b600054604051600160e01b63e3483a9d0281526001600160a01b0386811660048301908152600160648401819052608060248501908152608485018d9052929094169363e3483a9d9389938e938e93600893909290604481019060a401878780828437600083820152601f01601f1916909101848103835286546002600019610100600184161502019091160480825260209091019150869080156168e75780601f106168bc576101008083540402835291602001916168e7565b820191906000526020600020905b8154815290600101906020018083116168ca57829003601f168201915b5050975050505050505050600060405180830381600087803b158015612d4057600080fd5b6004805460408051600160e01b638c8642df0281526002602482018190529381019182528451604482015284516000946001600160a01b0390941693638c8642df93879391929091829160649091019060208601908083838c5b8381101561697e578181015183820152602001616966565b50505050905090810190601f1680156169ab5780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b1580156169c957600080fd5b505afa1580156169dd573d6000803e3d6000fd5b505050506040513d60208110156169f357600080fd5b505192915050565b6004805460408051600160e01b638c8642df028152602481018590529283019081528451604484015284516000936001600160a01b0390931692638c8642df9287928792829160649091019060208601908083838c83811015613e44578181015183820152602001613e2c565b600254604051600160e21b632c084e190281526001600160a01b03848116602483015260448201849052606060048301908152865160648401528651600094929092169263b02138649288928892889282916084019060208701908083838d5b83811015616ae0578181015183820152602001616ac8565b50505050905090810190601f168015616b0d5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015616b2e57600080fd5b505af1158015616b42573d6000803e3d6000fd5b505050506040513d6020811015616b5857600080fd5b5051949350505050565b600480546040517fffe40d1d00000000000000000000000000000000000000000000000000000000815260209281018381528451602483015284516000946001600160a01b039094169363ffe40d1d9387939283926044909201918501908083838b5b83811015616bdd578181015183820152602001616bc5565b50505050905090810190601f168015616c0a5780820380516001836020036101000a031916815260200191505b509250505060206040518083038186803b1580156169c957600080fd5b8015616d015760025460408051600160e01b635607395b0281526001600160a01b03858116602483015260048201928352865160448301528651931692635607395b9287928792829160640190602086019080838360005b83811015616c97578181015183820152602001616c7f565b50505050905090810190601f168015616cc45780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b158015616ce457600080fd5b505af1158015616cf8573d6000803e3d6000fd5b50505050616db8565b60025460408051600160e11b632ce5eb7f0281526001600160a01b038581166024830152600482019283528651604483015286519316926359cbd6fe9287928792829160640190602086019080838360005b83811015616d6b578181015183820152602001616d53565b50505050905090810190601f168015616d985780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b15801561298f57600080fd5b505050565b6001546000906001600160a01b031663abf5739f8484614a7d815b60048054604051600160e11b630bbe46c502815260209281018381528451602483015284516060946001600160a01b039094169363177c8d8a93879392839260449092019185019080838360005b83811015616e3e578181015183820152602001616e26565b50505050905090810190601f168015616e6b5780820380516001836020036101000a031916815260200191505b509250505060006040518083038186803b158015616e8857600080fd5b505afa158015616e9c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015616ec557600080fd5b810190808051600160201b811115616edc57600080fd5b82016020810184811115616eef57600080fd5b8151600160201b811182820187101715616f0857600080fd5b50909695505050505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10616f555782800160ff19823516178555616f82565b82800160010185558215616f82579182015b82811115616f82578235825591602001919060010190616f67565b50612ebc926131109250905b80821115612ebc5760008155600101616f8e56fe6f7267206e6f7420696e20617070726f76656420737461747573000000000000496e636f7272656374206e6574776f726b20626f6f7420737461747573000000696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f77656463616e2062652063616c6c656420627920696e7465726661636520636f6e7472616374206f6e6c796163636f756e74206973206e6f742061206e6574776f726b2061646d696e206163636f756e746163636f756e74206973206e6f742061206f72672061646d696e206163636f756e74a165627a7a7230582047eb5d5106c9085018e51aa9094a706fff1dfed139d9e1e83d83ee3d32fec65b0029" + +// DeployPermImpl deploys a new Ethereum contract, binding an instance of PermImpl to it. +func DeployPermImpl(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address, _orgManager common.Address, _rolesManager common.Address, _accountManager common.Address, _voterManager common.Address, _nodeManager common.Address) (common.Address, *types.Transaction, *PermImpl, error) { + parsed, err := abi.JSON(strings.NewReader(PermImplABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PermImplBin), backend, _permUpgradable, _orgManager, _rolesManager, _accountManager, _voterManager, _nodeManager) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &PermImpl{PermImplCaller: PermImplCaller{contract: contract}, PermImplTransactor: PermImplTransactor{contract: contract}, PermImplFilterer: PermImplFilterer{contract: contract}}, nil +} + +// PermImpl is an auto generated Go binding around an Ethereum contract. +type PermImpl struct { + PermImplCaller // Read-only binding to the contract + PermImplTransactor // Write-only binding to the contract + PermImplFilterer // Log filterer for contract events +} + +// PermImplCaller is an auto generated read-only Go binding around an Ethereum contract. +type PermImplCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermImplTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PermImplTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermImplFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PermImplFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermImplSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PermImplSession struct { + Contract *PermImpl // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermImplCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PermImplCallerSession struct { + Contract *PermImplCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PermImplTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PermImplTransactorSession struct { + Contract *PermImplTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermImplRaw is an auto generated low-level Go binding around an Ethereum contract. +type PermImplRaw struct { + Contract *PermImpl // Generic contract binding to access the raw methods on +} + +// PermImplCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PermImplCallerRaw struct { + Contract *PermImplCaller // Generic read-only contract binding to access the raw methods on +} + +// PermImplTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PermImplTransactorRaw struct { + Contract *PermImplTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPermImpl creates a new instance of PermImpl, bound to a specific deployed contract. +func NewPermImpl(address common.Address, backend bind.ContractBackend) (*PermImpl, error) { + contract, err := bindPermImpl(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PermImpl{PermImplCaller: PermImplCaller{contract: contract}, PermImplTransactor: PermImplTransactor{contract: contract}, PermImplFilterer: PermImplFilterer{contract: contract}}, nil +} + +// NewPermImplCaller creates a new read-only instance of PermImpl, bound to a specific deployed contract. +func NewPermImplCaller(address common.Address, caller bind.ContractCaller) (*PermImplCaller, error) { + contract, err := bindPermImpl(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PermImplCaller{contract: contract}, nil +} + +// NewPermImplTransactor creates a new write-only instance of PermImpl, bound to a specific deployed contract. +func NewPermImplTransactor(address common.Address, transactor bind.ContractTransactor) (*PermImplTransactor, error) { + contract, err := bindPermImpl(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PermImplTransactor{contract: contract}, nil +} + +// NewPermImplFilterer creates a new log filterer instance of PermImpl, bound to a specific deployed contract. +func NewPermImplFilterer(address common.Address, filterer bind.ContractFilterer) (*PermImplFilterer, error) { + contract, err := bindPermImpl(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PermImplFilterer{contract: contract}, nil +} + +// bindPermImpl binds a generic wrapper to an already deployed contract. +func bindPermImpl(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PermImplABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermImpl *PermImplRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermImpl.Contract.PermImplCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermImpl *PermImplRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermImpl.Contract.PermImplTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermImpl *PermImplRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermImpl.Contract.PermImplTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermImpl *PermImplCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermImpl.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermImpl *PermImplTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermImpl.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermImpl *PermImplTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermImpl.Contract.contract.Transact(opts, method, params...) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermImpl *PermImplCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "getNetworkBootStatus") + return *ret0, err +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermImpl *PermImplSession) GetNetworkBootStatus() (bool, error) { + return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermImpl *PermImplCallerSession) GetNetworkBootStatus() (bool, error) { + return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermImpl *PermImplCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(common.Address) + ret3 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _PermImpl.contract.Call(opts, out, "getPendingOp", _orgId) + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermImpl *PermImplSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermImpl *PermImplCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) +} + +// GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. +// +// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +func (_PermImpl *PermImplCaller) GetPolicyDetails(opts *bind.CallOpts) (string, string, string, bool, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(string) + ret3 = new(bool) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _PermImpl.contract.Call(opts, out, "getPolicyDetails") + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. +// +// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +func (_PermImpl *PermImplSession) GetPolicyDetails() (string, string, string, bool, error) { + return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) +} + +// GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. +// +// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +func (_PermImpl *PermImplCallerSession) GetPolicyDetails() (string, string, string, bool, error) { + return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermImpl *PermImplCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "isNetworkAdmin", _account) + return *ret0, err +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermImpl *PermImplSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermImpl *PermImplCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) + return *ret0, err +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "validateAccount", _account, _orgId) + return *ret0, err +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _account) returns() +func (_PermImpl *PermImplTransactor) AddAdminAccount(opts *bind.TransactOpts, _account common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addAdminAccount", _account) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _account) returns() +func (_PermImpl *PermImplSession) AddAdminAccount(_account common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminAccount(&_PermImpl.TransactOpts, _account) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _account) returns() +func (_PermImpl *PermImplTransactorSession) AddAdminAccount(_account common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminAccount(&_PermImpl.TransactOpts, _account) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x3f25c288. +// +// Solidity: function addAdminNode(string _enodeId) returns() +func (_PermImpl *PermImplTransactor) AddAdminNode(opts *bind.TransactOpts, _enodeId string) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addAdminNode", _enodeId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x3f25c288. +// +// Solidity: function addAdminNode(string _enodeId) returns() +func (_PermImpl *PermImplSession) AddAdminNode(_enodeId string) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminNode(&_PermImpl.TransactOpts, _enodeId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x3f25c288. +// +// Solidity: function addAdminNode(string _enodeId) returns() +func (_PermImpl *PermImplTransactorSession) AddAdminNode(_enodeId string) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminNode(&_PermImpl.TransactOpts, _enodeId) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x1b04c276. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddNewRole(opts *bind.TransactOpts, _roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addNewRole", _roleId, _orgId, _access, _voter, _admin, _caller) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x1b04c276. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin, address _caller) returns() +func (_PermImpl *PermImplSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNewRole(&_PermImpl.TransactOpts, _roleId, _orgId, _access, _voter, _admin, _caller) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x1b04c276. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNewRole(&_PermImpl.TransactOpts, _roleId, _orgId, _access, _voter, _admin, _caller) +} + +// AddNode is a paid mutator transaction binding the contract method 0x59a260a3. +// +// Solidity: function addNode(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddNode(opts *bind.TransactOpts, _orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addNode", _orgId, _enodeId, _caller) +} + +// AddNode is a paid mutator transaction binding the contract method 0x59a260a3. +// +// Solidity: function addNode(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplSession) AddNode(_orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNode(&_PermImpl.TransactOpts, _orgId, _enodeId, _caller) +} + +// AddNode is a paid mutator transaction binding the contract method 0x59a260a3. +// +// Solidity: function addNode(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddNode(_orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNode(&_PermImpl.TransactOpts, _orgId, _enodeId, _caller) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf922f802. +// +// Solidity: function addOrg(string _orgId, string _enodeId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addOrg", _orgId, _enodeId, _account, _caller) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf922f802. +// +// Solidity: function addOrg(string _orgId, string _enodeId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) AddOrg(_orgId string, _enodeId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _account, _caller) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf922f802. +// +// Solidity: function addOrg(string _orgId, string _enodeId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddOrg(_orgId string, _enodeId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _account, _caller) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0xa64d2860. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddSubOrg(opts *bind.TransactOpts, _pOrgId string, _orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addSubOrg", _pOrgId, _orgId, _enodeId, _caller) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0xa64d2860. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddSubOrg(&_PermImpl.TransactOpts, _pOrgId, _orgId, _enodeId, _caller) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0xa64d2860. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddSubOrg(&_PermImpl.TransactOpts, _pOrgId, _orgId, _enodeId, _caller) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x88843041. +// +// Solidity: function approveAdminRole(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveAdminRole", _orgId, _account, _caller) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x88843041. +// +// Solidity: function approveAdminRole(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveAdminRole(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x88843041. +// +// Solidity: function approveAdminRole(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveAdminRole(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x4b20f45f. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveBlacklistedAccountRecovery", _orgId, _account, _caller) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x4b20f45f. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x4b20f45f. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x655a8ef5. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveBlacklistedNodeRecovery", _orgId, _enodeId, _caller) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x655a8ef5. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _caller) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x655a8ef5. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _caller) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0x3bc07dea. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveOrg", _orgId, _enodeId, _account, _caller) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0x3bc07dea. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveOrg(_orgId string, _enodeId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _account, _caller) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0x3bc07dea. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveOrg(_orgId string, _enodeId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _account, _caller) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0xb5546564. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveOrgStatus", _orgId, _action, _caller) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0xb5546564. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0xb5546564. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x8baa8191. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactor) AssignAccountRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "assignAccountRole", _account, _orgId, _roleId, _caller) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x8baa8191. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, address _caller) returns() +func (_PermImpl *PermImplSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAccountRole(&_PermImpl.TransactOpts, _account, _orgId, _roleId, _caller) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x8baa8191. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAccountRole(&_PermImpl.TransactOpts, _account, _orgId, _roleId, _caller) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x404bf3eb. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactor) AssignAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "assignAdminRole", _orgId, _account, _roleId, _caller) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x404bf3eb. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId, address _caller) returns() +func (_PermImpl *PermImplSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _roleId, _caller) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x404bf3eb. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _roleId, _caller) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermImpl *PermImplTransactor) Init(opts *bind.TransactOpts, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "init", _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermImpl *PermImplSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermImpl.Contract.Init(&_PermImpl.TransactOpts, _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermImpl *PermImplTransactorSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermImpl.Contract.Init(&_PermImpl.TransactOpts, _breadth, _depth) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0x5ca5adbe. +// +// Solidity: function removeRole(string _roleId, string _orgId, address _caller) returns() +func (_PermImpl *PermImplTransactor) RemoveRole(opts *bind.TransactOpts, _roleId string, _orgId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "removeRole", _roleId, _orgId, _caller) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0x5ca5adbe. +// +// Solidity: function removeRole(string _roleId, string _orgId, address _caller) returns() +func (_PermImpl *PermImplSession) RemoveRole(_roleId string, _orgId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.RemoveRole(&_PermImpl.TransactOpts, _roleId, _orgId, _caller) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0x5ca5adbe. +// +// Solidity: function removeRole(string _roleId, string _orgId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) RemoveRole(_roleId string, _orgId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.RemoveRole(&_PermImpl.TransactOpts, _roleId, _orgId, _caller) +} + +// SetMigrationPolicy is a paid mutator transaction binding the contract method 0xf5ad584a. +// +// Solidity: function setMigrationPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole, bool _networkBootStatus) returns() +func (_PermImpl *PermImplTransactor) SetMigrationPolicy(opts *bind.TransactOpts, _nwAdminOrg string, _nwAdminRole string, _oAdminRole string, _networkBootStatus bool) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "setMigrationPolicy", _nwAdminOrg, _nwAdminRole, _oAdminRole, _networkBootStatus) +} + +// SetMigrationPolicy is a paid mutator transaction binding the contract method 0xf5ad584a. +// +// Solidity: function setMigrationPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole, bool _networkBootStatus) returns() +func (_PermImpl *PermImplSession) SetMigrationPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string, _networkBootStatus bool) (*types.Transaction, error) { + return _PermImpl.Contract.SetMigrationPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole, _networkBootStatus) +} + +// SetMigrationPolicy is a paid mutator transaction binding the contract method 0xf5ad584a. +// +// Solidity: function setMigrationPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole, bool _networkBootStatus) returns() +func (_PermImpl *PermImplTransactorSession) SetMigrationPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string, _networkBootStatus bool) (*types.Transaction, error) { + return _PermImpl.Contract.SetMigrationPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole, _networkBootStatus) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermImpl *PermImplTransactor) SetPolicy(opts *bind.TransactOpts, _nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "setPolicy", _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermImpl *PermImplSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermImpl.Contract.SetPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermImpl *PermImplTransactorSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermImpl.Contract.SetPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x1c249912. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) StartBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "startBlacklistedAccountRecovery", _orgId, _account, _caller) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x1c249912. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x1c249912. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xc3dc8e09. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactor) StartBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "startBlacklistedNodeRecovery", _orgId, _enodeId, _caller) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xc3dc8e09. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _caller) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xc3dc8e09. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _caller) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x04e81f1e. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) UpdateAccountStatus(opts *bind.TransactOpts, _orgId string, _account common.Address, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateAccountStatus", _orgId, _account, _action, _caller) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x04e81f1e. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateAccountStatus(&_PermImpl.TransactOpts, _orgId, _account, _action, _caller) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x04e81f1e. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateAccountStatus(&_PermImpl.TransactOpts, _orgId, _account, _action, _caller) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermImpl *PermImplTransactor) UpdateNetworkBootStatus(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateNetworkBootStatus") +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermImpl *PermImplSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNetworkBootStatus(&_PermImpl.TransactOpts) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermImpl *PermImplTransactorSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNetworkBootStatus(&_PermImpl.TransactOpts) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xdbfad711. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) UpdateNodeStatus(opts *bind.TransactOpts, _orgId string, _enodeId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateNodeStatus", _orgId, _enodeId, _action, _caller) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xdbfad711. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) UpdateNodeStatus(_orgId string, _enodeId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNodeStatus(&_PermImpl.TransactOpts, _orgId, _enodeId, _action, _caller) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xdbfad711. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) UpdateNodeStatus(_orgId string, _enodeId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNodeStatus(&_PermImpl.TransactOpts, _orgId, _enodeId, _action, _caller) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0x3cf5f33b. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) UpdateOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateOrgStatus", _orgId, _action, _caller) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0x3cf5f33b. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) UpdateOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0x3cf5f33b. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) UpdateOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// PermImplPermissionsInitializedIterator is returned from FilterPermissionsInitialized and is used to iterate over the raw logs and unpacked data for PermissionsInitialized events raised by the PermImpl contract. +type PermImplPermissionsInitializedIterator struct { + Event *PermImplPermissionsInitialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PermImplPermissionsInitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PermImplPermissionsInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PermImplPermissionsInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PermImplPermissionsInitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PermImplPermissionsInitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PermImplPermissionsInitialized represents a PermissionsInitialized event raised by the PermImpl contract. +type PermImplPermissionsInitialized struct { + NetworkBootStatus bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterPermissionsInitialized is a free log retrieval operation binding the contract event 0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf. +// +// Solidity: event PermissionsInitialized(bool _networkBootStatus) +func (_PermImpl *PermImplFilterer) FilterPermissionsInitialized(opts *bind.FilterOpts) (*PermImplPermissionsInitializedIterator, error) { + + logs, sub, err := _PermImpl.contract.FilterLogs(opts, "PermissionsInitialized") + if err != nil { + return nil, err + } + return &PermImplPermissionsInitializedIterator{contract: _PermImpl.contract, event: "PermissionsInitialized", logs: logs, sub: sub}, nil +} + +var PermissionsInitializedTopicHash = "0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf" + +// WatchPermissionsInitialized is a free log subscription operation binding the contract event 0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf. +// +// Solidity: event PermissionsInitialized(bool _networkBootStatus) +func (_PermImpl *PermImplFilterer) WatchPermissionsInitialized(opts *bind.WatchOpts, sink chan<- *PermImplPermissionsInitialized) (event.Subscription, error) { + + logs, sub, err := _PermImpl.contract.WatchLogs(opts, "PermissionsInitialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PermImplPermissionsInitialized) + if err := _PermImpl.contract.UnpackLog(event, "PermissionsInitialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParsePermissionsInitialized is a log parse operation binding the contract event 0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf. +// +// Solidity: event PermissionsInitialized(bool _networkBootStatus) +func (_PermImpl *PermImplFilterer) ParsePermissionsInitialized(log types.Log) (*PermImplPermissionsInitialized, error) { + event := new(PermImplPermissionsInitialized) + if err := _PermImpl.contract.UnpackLog(event, "PermissionsInitialized", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v1/bind/permission_interface.go b/permission/v1/bind/permission_interface.go new file mode 100644 index 0000000000..39468af8ef --- /dev/null +++ b/permission/v1/bind/permission_interface.go @@ -0,0 +1,840 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PermInterfaceABI is the input ABI used to generate the binding from. +const PermInterfaceABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"getPermissionsImpl\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateNodeStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"approveAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminOrg\",\"type\":\"string\"},{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"}],\"name\":\"setPolicy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"}],\"name\":\"assignAccountRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"approveBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"}],\"name\":\"addAdminNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_roleId\",\"type\":\"string\"}],\"name\":\"assignAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updateNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_pOrgId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"}],\"name\":\"addSubOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_acct\",\"type\":\"address\"}],\"name\":\"addAdminAccount\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_permImplementation\",\"type\":\"address\"}],\"name\":\"setPermImplementation\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_access\",\"type\":\"uint256\"},{\"name\":\"_voter\",\"type\":\"bool\"},{\"name\":\"_admin\",\"type\":\"bool\"}],\"name\":\"addNewRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"}],\"name\":\"approveBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"approveOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"validateAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"approveOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateAccountStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"}],\"name\":\"startBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"addOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"isOrgAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_breadth\",\"type\":\"uint256\"},{\"name\":\"_depth\",\"type\":\"uint256\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"removeRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"startBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"}],\"name\":\"addNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"isNetworkAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getPendingOp\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permImplUpgradeable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" + +var PermInterfaceParsedABI, _ = abi.JSON(strings.NewReader(PermInterfaceABI)) + +// PermInterfaceBin is the compiled bytecode used for deploying new contracts. +var PermInterfaceBin = "0x608060405234801561001057600080fd5b506040516020806125aa8339810180604052602081101561003057600080fd5b5051600280546001600160a01b0319166001600160a01b0390921691909117905561254a806100606000396000f3fe608060405234801561001057600080fd5b50600436106101cf5760003560e01c80635adbfa7a116101045780639bd38101116100a2578063a97a440611610071578063a97a440614610f50578063bb3b6e801461100e578063d1aa0c201461107c578063f346a3a7146110a2576101cf565b80639bd3810114610d7a578063a5843f0814610df8578063a634301214610e1b578063a97914bf14610ed9576101cf565b80637e461258116100de5780637e46125814610ab157806384b7a84a14610b785780638cb58ef314610bf55780638f362a3e14610cb3576101cf565b80635adbfa7a146109075780635be9672c146109c55780636b568d7614610a33576101cf565b806343de646c116101715780634cff819e1161014b5780634cff819e146106df5780634fe57e7a146107ed578063511bbd9f1461081357806351f604c314610839576101cf565b806343de646c146105f057806344478e79146106bb5780634cbfa82e146106d7576101cf565b80631b610220116101ad5780631b6102201461032f5780632f7f0a121461043d5780633e239b231461050b5780633f25c28814610582576101cf565b806303ed6933146101d45780630cc50146146101f857806316724c44146102b8575b600080fd5b6101dc61120e565b604080516001600160a01b039092168252519081900360200190f35b6102b66004803603606081101561020e57600080fd5b810190602081018135600160201b81111561022857600080fd5b82018360208201111561023a57600080fd5b803590602001918460018302840111600160201b8311171561025b57600080fd5b919390929091602081019035600160201b81111561027857600080fd5b82018360208201111561028a57600080fd5b803590602001918460018302840111600160201b831117156102ab57600080fd5b91935091503561121d565b005b6102b6600480360360408110156102ce57600080fd5b810190602081018135600160201b8111156102e857600080fd5b8201836020820111156102fa57600080fd5b803590602001918460018302840111600160201b8311171561031b57600080fd5b9193509150356001600160a01b03166112fa565b6102b66004803603606081101561034557600080fd5b810190602081018135600160201b81111561035f57600080fd5b82018360208201111561037157600080fd5b803590602001918460018302840111600160201b8311171561039257600080fd5b919390929091602081019035600160201b8111156103af57600080fd5b8201836020820111156103c157600080fd5b803590602001918460018302840111600160201b831117156103e257600080fd5b919390929091602081019035600160201b8111156103ff57600080fd5b82018360208201111561041157600080fd5b803590602001918460018302840111600160201b8311171561043257600080fd5b5090925090506113a9565b6102b66004803603606081101561045357600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561047d57600080fd5b82018360208201111561048f57600080fd5b803590602001918460018302840111600160201b831117156104b057600080fd5b919390929091602081019035600160201b8111156104cd57600080fd5b8201836020820111156104df57600080fd5b803590602001918460018302840111600160201b8311171561050057600080fd5b5090925090506114a0565b6102b66004803603604081101561052157600080fd5b810190602081018135600160201b81111561053b57600080fd5b82018360208201111561054d57600080fd5b803590602001918460018302840111600160201b8311171561056e57600080fd5b9193509150356001600160a01b0316611562565b6102b66004803603602081101561059857600080fd5b810190602081018135600160201b8111156105b257600080fd5b8201836020820111156105c457600080fd5b803590602001918460018302840111600160201b831117156105e557600080fd5b5090925090506115f4565b6102b66004803603606081101561060657600080fd5b810190602081018135600160201b81111561062057600080fd5b82018360208201111561063257600080fd5b803590602001918460018302840111600160201b8311171561065357600080fd5b919390926001600160a01b0383351692604081019060200135600160201b81111561067d57600080fd5b82018360208201111561068f57600080fd5b803590602001918460018302840111600160201b831117156106b057600080fd5b50909250905061168a565b6106c3611749565b604080519115158252519081900360200190f35b6106c36117cb565b6102b6600480360360608110156106f557600080fd5b810190602081018135600160201b81111561070f57600080fd5b82018360208201111561072157600080fd5b803590602001918460018302840111600160201b8311171561074257600080fd5b919390929091602081019035600160201b81111561075f57600080fd5b82018360208201111561077157600080fd5b803590602001918460018302840111600160201b8311171561079257600080fd5b919390929091602081019035600160201b8111156107af57600080fd5b8201836020820111156107c157600080fd5b803590602001918460018302840111600160201b831117156107e257600080fd5b50909250905061182e565b6102b66004803603602081101561080357600080fd5b50356001600160a01b031661190f565b6102b66004803603602081101561082957600080fd5b50356001600160a01b0316611978565b6102b6600480360360a081101561084f57600080fd5b810190602081018135600160201b81111561086957600080fd5b82018360208201111561087b57600080fd5b803590602001918460018302840111600160201b8311171561089c57600080fd5b919390929091602081019035600160201b8111156108b957600080fd5b8201836020820111156108cb57600080fd5b803590602001918460018302840111600160201b831117156108ec57600080fd5b919350915080359060208101351515906040013515156119fc565b6102b66004803603604081101561091d57600080fd5b810190602081018135600160201b81111561093757600080fd5b82018360208201111561094957600080fd5b803590602001918460018302840111600160201b8311171561096a57600080fd5b919390929091602081019035600160201b81111561098757600080fd5b82018360208201111561099957600080fd5b803590602001918460018302840111600160201b831117156109ba57600080fd5b509092509050611af1565b6102b6600480360360408110156109db57600080fd5b810190602081018135600160201b8111156109f557600080fd5b820183602082011115610a0757600080fd5b803590602001918460018302840111600160201b83111715610a2857600080fd5b919350915035611bc3565b6106c360048036036040811015610a4957600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610a7357600080fd5b820183602082011115610a8557600080fd5b803590602001918460018302840111600160201b83111715610aa657600080fd5b509092509050611c50565b6102b660048036036060811015610ac757600080fd5b810190602081018135600160201b811115610ae157600080fd5b820183602082011115610af357600080fd5b803590602001918460018302840111600160201b83111715610b1457600080fd5b919390929091602081019035600160201b811115610b3157600080fd5b820183602082011115610b4357600080fd5b803590602001918460018302840111600160201b83111715610b6457600080fd5b9193509150356001600160a01b0316611d08565b6102b660048036036060811015610b8e57600080fd5b810190602081018135600160201b811115610ba857600080fd5b820183602082011115610bba57600080fd5b803590602001918460018302840111600160201b83111715610bdb57600080fd5b91935091506001600160a01b038135169060200135611dc7565b6102b660048036036040811015610c0b57600080fd5b810190602081018135600160201b811115610c2557600080fd5b820183602082011115610c3757600080fd5b803590602001918460018302840111600160201b83111715610c5857600080fd5b919390929091602081019035600160201b811115610c7557600080fd5b820183602082011115610c8757600080fd5b803590602001918460018302840111600160201b83111715610ca857600080fd5b509092509050611e61565b6102b660048036036060811015610cc957600080fd5b810190602081018135600160201b811115610ce357600080fd5b820183602082011115610cf557600080fd5b803590602001918460018302840111600160201b83111715610d1657600080fd5b919390929091602081019035600160201b811115610d3357600080fd5b820183602082011115610d4557600080fd5b803590602001918460018302840111600160201b83111715610d6657600080fd5b9193509150356001600160a01b0316611f15565b6106c360048036036040811015610d9057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610dba57600080fd5b820183602082011115610dcc57600080fd5b803590602001918460018302840111600160201b83111715610ded57600080fd5b509092509050611fd4565b6102b660048036036040811015610e0e57600080fd5b5080359060200135612058565b6102b660048036036040811015610e3157600080fd5b810190602081018135600160201b811115610e4b57600080fd5b820183602082011115610e5d57600080fd5b803590602001918460018302840111600160201b83111715610e7e57600080fd5b919390929091602081019035600160201b811115610e9b57600080fd5b820183602082011115610ead57600080fd5b803590602001918460018302840111600160201b83111715610ece57600080fd5b5090925090506120aa565b6102b660048036036040811015610eef57600080fd5b810190602081018135600160201b811115610f0957600080fd5b820183602082011115610f1b57600080fd5b803590602001918460018302840111600160201b83111715610f3c57600080fd5b9193509150356001600160a01b031661215e565b6102b660048036036040811015610f6657600080fd5b810190602081018135600160201b811115610f8057600080fd5b820183602082011115610f9257600080fd5b803590602001918460018302840111600160201b83111715610fb357600080fd5b919390929091602081019035600160201b811115610fd057600080fd5b820183602082011115610fe257600080fd5b803590602001918460018302840111600160201b8311171561100357600080fd5b5090925090506121f0565b6102b66004803603604081101561102457600080fd5b810190602081018135600160201b81111561103e57600080fd5b82018360208201111561105057600080fd5b803590602001918460018302840111600160201b8311171561107157600080fd5b9193509150356122a4565b6106c36004803603602081101561109257600080fd5b50356001600160a01b0316612331565b611110600480360360208110156110b857600080fd5b810190602081018135600160201b8111156110d257600080fd5b8201836020820111156110e457600080fd5b803590602001918460018302840111600160201b8311171561110557600080fd5b5090925090506123b4565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b8381101561116f578181015183820152602001611157565b50505050905090810190601f16801561119c5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b838110156111cf5781810151838201526020016111b7565b50505050905090810190601f1680156111fc5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b6000546001600160a01b031690565b600054604051600160e01b63dbfad711028152604481018390523360648201819052608060048301908152608483018890526001600160a01b039093169263dbfad711928992899289928992899290918190602481019060a401898980828437600083820152601f01601f191690910184810383528781526020019050878780828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b505af11580156112ef573d6000803e3d6000fd5b505050505050505050565b600054604051600160e01b63888430410281526001600160a01b03838116602483015233604483018190526060600484019081526064840187905291909316926388843041928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b505af11580156113a0573d6000803e3d6000fd5b50505050505050565b600054604051600160e51b62db0811028152606060048201908152606482018890526001600160a01b0390921691631b610220918991899189918991899189918190602481019060448101906084018a8a80828437600083820152601f01601f191690910185810384528881526020019050888880828437600083820152601f01601f191690910185810383528681526020019050868680828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b15801561148057600080fd5b505af1158015611494573d6000803e3d6000fd5b50505050505050505050565b600054604051600160e01b638baa81910281526001600160a01b03878116600483019081523360648401819052608060248501908152608485018990529290941693638baa8191938a938a938a938a938a9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b600054604051600160e01b634b20f45f0281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692634b20f45f928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b600054604051600160e31b6307e4b851028152602060048201908152602482018490526001600160a01b0390921691633f25c28891859185918190604401848480828437600081840152601f19601f8201169050808301925050509350505050600060405180830381600087803b15801561166e57600080fd5b505af1158015611682573d6000803e3d6000fd5b505050505050565b600054604051600160e01b63404bf3eb0281526001600160a01b038581166024830152336064830181905260806004840190815260848401899052919093169263404bf3eb9289928992899289928992918190604481019060a401898980828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b03166344478e796040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561179a57600080fd5b505af11580156117ae573d6000803e3d6000fd5b505050506040513d60208110156117c457600080fd5b5051905090565b60008060009054906101000a90046001600160a01b03166001600160a01b0316634cbfa82e6040518163ffffffff1660e01b815260040160206040518083038186803b15801561181a57600080fd5b505afa1580156117ae573d6000803e3d6000fd5b600054604051600160e51b63053269430281523360648201819052608060048301908152608483018990526001600160a01b039093169263a64d2860928a928a928a928a928a928a9281906024810190604481019060a4018b8b80828437600083820152601f01601f191690910185810384528981526020019050898980828437600083820152601f01601f191690910185810383528781526020019050878780828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b15801561148057600080fd5b6000805460408051600160e11b6327f2bf3d0281526001600160a01b03858116600483015291519190921692634fe57e7a926024808201939182900301818387803b15801561195d57600080fd5b505af1158015611971573d6000803e3d6000fd5b5050505050565b6002546001600160a01b031633146119da5760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b600054604051600160e11b630d82613b02815260448101859052831515606482015282151560848201523360a4820181905260c06004830190815260c483018a90526001600160a01b0390931692631b04c276928b928b928b928b928b928b928b9291908190602481019060e4018b8b80828437600083820152601f01601f191690910184810383528981526020019050898980828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b158015611ad057600080fd5b505af1158015611ae4573d6000803e3d6000fd5b5050505050505050505050565b600054604051600160e01b63655a8ef50281523360448201819052606060048301908152606483018790526001600160a01b039093169263655a8ef5928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b505af1158015611bb9573d6000803e3d6000fd5b5050505050505050565b600054604051600160e21b632d551959028152602481018390523360448201819052606060048301908152606483018690526001600160a01b039093169263b5546564928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b6000805460408051600160e11b6335ab46bb0281526001600160a01b03878116600483019081526024830193845260448301879052931692636b568d76928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015611cd457600080fd5b505afa158015611ce8573d6000803e3d6000fd5b505050506040513d6020811015611cfe57600080fd5b5051949350505050565b600054604051600160e11b631de03ef50281526001600160a01b0383811660448301523360648301819052608060048401908152608484018990529190931692633bc07dea9289928992899289928992918190602481019060a401898980828437600083820152601f01601f191690910184810383528781526020019050878780828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b600054604051600160e11b6302740f8f0281526001600160a01b0384811660248301526044820184905233606483018190526080600484019081526084840188905291909316926304e81f1e92889288928892889290819060a401878780828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e01b63c3dc8e090281523360448201819052606060048301908152606483018790526001600160a01b039093169263c3dc8e09928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e11b637c917c010281526001600160a01b038381166044830152336064830181905260806004840190815260848401899052919093169263f922f8029289928992899289928992918190602481019060a401898980828437600083820152601f01601f191690910184810383528781526020019050878780828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b6000805460408051600160e01b639bd381010281526001600160a01b03878116600483019081526024830193845260448301879052931692639bd38101928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015611cd457600080fd5b6000805460408051600160e31b6314b087e1028152600481018690526024810185905290516001600160a01b039092169263a5843f089260448084019382900301818387803b15801561166e57600080fd5b600054604051600160e11b632e52d6df0281523360448201819052606060048301908152606483018790526001600160a01b0390931692635ca5adbe928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e11b630e124c890281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692631c249912928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b600054604051600160e01b6359a260a30281523360448201819052606060048301908152606483018790526001600160a01b03909316926359a260a3928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e01b633cf5f33b028152602481018390523360448201819052606060048301908152606483018690526001600160a01b0390931692633cf5f33b928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b6000805460408051600160e51b63068d50610281526001600160a01b0385811660048301529151919092169163d1aa0c20916024808301926020929190829003018186803b15801561238257600080fd5b505afa158015612396573d6000803e3d6000fd5b505050506040513d60208110156123ac57600080fd5b505192915050565b60008054604051600160e01b63f346a3a7028152602060048201908152602482018590526060938493909283926001600160a01b039092169163f346a3a791899189918190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561243657600080fd5b505afa15801561244a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052608081101561247357600080fd5b810190808051600160201b81111561248a57600080fd5b8201602081018481111561249d57600080fd5b8151600160201b8111828201871017156124b657600080fd5b50509291906020018051600160201b8111156124d157600080fd5b820160208101848111156124e457600080fd5b8151600160201b8111828201871017156124fd57600080fd5b50506020820151604090920151949b909a509098509296509194505050505056fea165627a7a72305820c59bf7b1eb3a15d1406b140bc566b70353e3ef021637abb4ecb03c63261f92b10029" + +// DeployPermInterface deploys a new Ethereum contract, binding an instance of PermInterface to it. +func DeployPermInterface(auth *bind.TransactOpts, backend bind.ContractBackend, _permImplUpgradeable common.Address) (common.Address, *types.Transaction, *PermInterface, error) { + parsed, err := abi.JSON(strings.NewReader(PermInterfaceABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PermInterfaceBin), backend, _permImplUpgradeable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &PermInterface{PermInterfaceCaller: PermInterfaceCaller{contract: contract}, PermInterfaceTransactor: PermInterfaceTransactor{contract: contract}, PermInterfaceFilterer: PermInterfaceFilterer{contract: contract}}, nil +} + +// PermInterface is an auto generated Go binding around an Ethereum contract. +type PermInterface struct { + PermInterfaceCaller // Read-only binding to the contract + PermInterfaceTransactor // Write-only binding to the contract + PermInterfaceFilterer // Log filterer for contract events +} + +// PermInterfaceCaller is an auto generated read-only Go binding around an Ethereum contract. +type PermInterfaceCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermInterfaceTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PermInterfaceTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermInterfaceFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PermInterfaceFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermInterfaceSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PermInterfaceSession struct { + Contract *PermInterface // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermInterfaceCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PermInterfaceCallerSession struct { + Contract *PermInterfaceCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PermInterfaceTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PermInterfaceTransactorSession struct { + Contract *PermInterfaceTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermInterfaceRaw is an auto generated low-level Go binding around an Ethereum contract. +type PermInterfaceRaw struct { + Contract *PermInterface // Generic contract binding to access the raw methods on +} + +// PermInterfaceCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PermInterfaceCallerRaw struct { + Contract *PermInterfaceCaller // Generic read-only contract binding to access the raw methods on +} + +// PermInterfaceTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PermInterfaceTransactorRaw struct { + Contract *PermInterfaceTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPermInterface creates a new instance of PermInterface, bound to a specific deployed contract. +func NewPermInterface(address common.Address, backend bind.ContractBackend) (*PermInterface, error) { + contract, err := bindPermInterface(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PermInterface{PermInterfaceCaller: PermInterfaceCaller{contract: contract}, PermInterfaceTransactor: PermInterfaceTransactor{contract: contract}, PermInterfaceFilterer: PermInterfaceFilterer{contract: contract}}, nil +} + +// NewPermInterfaceCaller creates a new read-only instance of PermInterface, bound to a specific deployed contract. +func NewPermInterfaceCaller(address common.Address, caller bind.ContractCaller) (*PermInterfaceCaller, error) { + contract, err := bindPermInterface(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PermInterfaceCaller{contract: contract}, nil +} + +// NewPermInterfaceTransactor creates a new write-only instance of PermInterface, bound to a specific deployed contract. +func NewPermInterfaceTransactor(address common.Address, transactor bind.ContractTransactor) (*PermInterfaceTransactor, error) { + contract, err := bindPermInterface(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PermInterfaceTransactor{contract: contract}, nil +} + +// NewPermInterfaceFilterer creates a new log filterer instance of PermInterface, bound to a specific deployed contract. +func NewPermInterfaceFilterer(address common.Address, filterer bind.ContractFilterer) (*PermInterfaceFilterer, error) { + contract, err := bindPermInterface(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PermInterfaceFilterer{contract: contract}, nil +} + +// bindPermInterface binds a generic wrapper to an already deployed contract. +func bindPermInterface(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PermInterfaceABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermInterface *PermInterfaceRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermInterface.Contract.PermInterfaceCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermInterface *PermInterfaceRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermInterface.Contract.PermInterfaceTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermInterface *PermInterfaceRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermInterface.Contract.PermInterfaceTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermInterface *PermInterfaceCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermInterface.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermInterface *PermInterfaceTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermInterface.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermInterface *PermInterfaceTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermInterface.Contract.contract.Transact(opts, method, params...) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermInterface *PermInterfaceCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "getNetworkBootStatus") + return *ret0, err +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermInterface *PermInterfaceSession) GetNetworkBootStatus() (bool, error) { + return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) GetNetworkBootStatus() (bool, error) { + return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermInterface *PermInterfaceCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(common.Address) + ret3 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _PermInterface.contract.Call(opts, out, "getPendingOp", _orgId) + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermInterface *PermInterfaceSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermInterface *PermInterfaceCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) +} + +// GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. +// +// Solidity: function getPermissionsImpl() constant returns(address) +func (_PermInterface *PermInterfaceCaller) GetPermissionsImpl(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "getPermissionsImpl") + return *ret0, err +} + +// GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. +// +// Solidity: function getPermissionsImpl() constant returns(address) +func (_PermInterface *PermInterfaceSession) GetPermissionsImpl() (common.Address, error) { + return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) +} + +// GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. +// +// Solidity: function getPermissionsImpl() constant returns(address) +func (_PermInterface *PermInterfaceCallerSession) GetPermissionsImpl() (common.Address, error) { + return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "isNetworkAdmin", _account) + return *ret0, err +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermInterface *PermInterfaceSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) + return *ret0, err +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "validateAccount", _account, _orgId) + return *ret0, err +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _acct) returns() +func (_PermInterface *PermInterfaceTransactor) AddAdminAccount(opts *bind.TransactOpts, _acct common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addAdminAccount", _acct) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _acct) returns() +func (_PermInterface *PermInterfaceSession) AddAdminAccount(_acct common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminAccount(&_PermInterface.TransactOpts, _acct) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _acct) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddAdminAccount(_acct common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminAccount(&_PermInterface.TransactOpts, _acct) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x3f25c288. +// +// Solidity: function addAdminNode(string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactor) AddAdminNode(opts *bind.TransactOpts, _enodeId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addAdminNode", _enodeId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x3f25c288. +// +// Solidity: function addAdminNode(string _enodeId) returns() +func (_PermInterface *PermInterfaceSession) AddAdminNode(_enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminNode(&_PermInterface.TransactOpts, _enodeId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x3f25c288. +// +// Solidity: function addAdminNode(string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddAdminNode(_enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminNode(&_PermInterface.TransactOpts, _enodeId) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x51f604c3. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin) returns() +func (_PermInterface *PermInterfaceTransactor) AddNewRole(opts *bind.TransactOpts, _roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addNewRole", _roleId, _orgId, _access, _voter, _admin) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x51f604c3. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin) returns() +func (_PermInterface *PermInterfaceSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool) (*types.Transaction, error) { + return _PermInterface.Contract.AddNewRole(&_PermInterface.TransactOpts, _roleId, _orgId, _access, _voter, _admin) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x51f604c3. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool) (*types.Transaction, error) { + return _PermInterface.Contract.AddNewRole(&_PermInterface.TransactOpts, _roleId, _orgId, _access, _voter, _admin) +} + +// AddNode is a paid mutator transaction binding the contract method 0xa97a4406. +// +// Solidity: function addNode(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactor) AddNode(opts *bind.TransactOpts, _orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addNode", _orgId, _enodeId) +} + +// AddNode is a paid mutator transaction binding the contract method 0xa97a4406. +// +// Solidity: function addNode(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceSession) AddNode(_orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.AddNode(&_PermInterface.TransactOpts, _orgId, _enodeId) +} + +// AddNode is a paid mutator transaction binding the contract method 0xa97a4406. +// +// Solidity: function addNode(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddNode(_orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.AddNode(&_PermInterface.TransactOpts, _orgId, _enodeId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0x8f362a3e. +// +// Solidity: function addOrg(string _orgId, string _enodeId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) AddOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addOrg", _orgId, _enodeId, _account) +} + +// AddOrg is a paid mutator transaction binding the contract method 0x8f362a3e. +// +// Solidity: function addOrg(string _orgId, string _enodeId, address _account) returns() +func (_PermInterface *PermInterfaceSession) AddOrg(_orgId string, _enodeId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _account) +} + +// AddOrg is a paid mutator transaction binding the contract method 0x8f362a3e. +// +// Solidity: function addOrg(string _orgId, string _enodeId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddOrg(_orgId string, _enodeId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _account) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x4cff819e. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactor) AddSubOrg(opts *bind.TransactOpts, _pOrgId string, _orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addSubOrg", _pOrgId, _orgId, _enodeId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x4cff819e. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.AddSubOrg(&_PermInterface.TransactOpts, _pOrgId, _orgId, _enodeId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x4cff819e. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.AddSubOrg(&_PermInterface.TransactOpts, _pOrgId, _orgId, _enodeId) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x16724c44. +// +// Solidity: function approveAdminRole(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveAdminRole", _orgId, _account) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x16724c44. +// +// Solidity: function approveAdminRole(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceSession) ApproveAdminRole(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveAdminRole(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x16724c44. +// +// Solidity: function approveAdminRole(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveAdminRole(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveAdminRole(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x3e239b23. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveBlacklistedAccountRecovery", _orgId, _account) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x3e239b23. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x3e239b23. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x5adbfa7a. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveBlacklistedNodeRecovery", _orgId, _enodeId) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x5adbfa7a. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x5adbfa7a. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0x7e461258. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveOrg", _orgId, _enodeId, _account) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0x7e461258. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, address _account) returns() +func (_PermInterface *PermInterfaceSession) ApproveOrg(_orgId string, _enodeId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _account) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0x7e461258. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveOrg(_orgId string, _enodeId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _account) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0x5be9672c. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveOrgStatus", _orgId, _action) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0x5be9672c. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) ApproveOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0x5be9672c. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x2f7f0a12. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactor) AssignAccountRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "assignAccountRole", _account, _orgId, _roleId) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x2f7f0a12. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId) returns() +func (_PermInterface *PermInterfaceSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAccountRole(&_PermInterface.TransactOpts, _account, _orgId, _roleId) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x2f7f0a12. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAccountRole(&_PermInterface.TransactOpts, _account, _orgId, _roleId) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x43de646c. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactor) AssignAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address, _roleId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "assignAdminRole", _orgId, _account, _roleId) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x43de646c. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId) returns() +func (_PermInterface *PermInterfaceSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAdminRole(&_PermInterface.TransactOpts, _orgId, _account, _roleId) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x43de646c. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAdminRole(&_PermInterface.TransactOpts, _orgId, _account, _roleId) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermInterface *PermInterfaceTransactor) Init(opts *bind.TransactOpts, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "init", _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermInterface *PermInterfaceSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.Init(&_PermInterface.TransactOpts, _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermInterface *PermInterfaceTransactorSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.Init(&_PermInterface.TransactOpts, _breadth, _depth) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_PermInterface *PermInterfaceTransactor) RemoveRole(opts *bind.TransactOpts, _roleId string, _orgId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "removeRole", _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_PermInterface *PermInterfaceSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _PermInterface.Contract.RemoveRole(&_PermInterface.TransactOpts, _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_PermInterface *PermInterfaceTransactorSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _PermInterface.Contract.RemoveRole(&_PermInterface.TransactOpts, _roleId, _orgId) +} + +// SetPermImplementation is a paid mutator transaction binding the contract method 0x511bbd9f. +// +// Solidity: function setPermImplementation(address _permImplementation) returns() +func (_PermInterface *PermInterfaceTransactor) SetPermImplementation(opts *bind.TransactOpts, _permImplementation common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "setPermImplementation", _permImplementation) +} + +// SetPermImplementation is a paid mutator transaction binding the contract method 0x511bbd9f. +// +// Solidity: function setPermImplementation(address _permImplementation) returns() +func (_PermInterface *PermInterfaceSession) SetPermImplementation(_permImplementation common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.SetPermImplementation(&_PermInterface.TransactOpts, _permImplementation) +} + +// SetPermImplementation is a paid mutator transaction binding the contract method 0x511bbd9f. +// +// Solidity: function setPermImplementation(address _permImplementation) returns() +func (_PermInterface *PermInterfaceTransactorSession) SetPermImplementation(_permImplementation common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.SetPermImplementation(&_PermInterface.TransactOpts, _permImplementation) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermInterface *PermInterfaceTransactor) SetPolicy(opts *bind.TransactOpts, _nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "setPolicy", _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermInterface *PermInterfaceSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermInterface.Contract.SetPolicy(&_PermInterface.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermInterface *PermInterfaceTransactorSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermInterface.Contract.SetPolicy(&_PermInterface.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0xa97914bf. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) StartBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "startBlacklistedAccountRecovery", _orgId, _account) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0xa97914bf. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0xa97914bf. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x8cb58ef3. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactor) StartBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "startBlacklistedNodeRecovery", _orgId, _enodeId) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x8cb58ef3. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x8cb58ef3. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId) returns() +func (_PermInterface *PermInterfaceTransactorSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) UpdateAccountStatus(opts *bind.TransactOpts, _orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateAccountStatus", _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateAccountStatus(&_PermInterface.TransactOpts, _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateAccountStatus(&_PermInterface.TransactOpts, _orgId, _account, _action) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermInterface *PermInterfaceTransactor) UpdateNetworkBootStatus(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateNetworkBootStatus") +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermInterface *PermInterfaceSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNetworkBootStatus(&_PermInterface.TransactOpts) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermInterface *PermInterfaceTransactorSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNetworkBootStatus(&_PermInterface.TransactOpts) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x0cc50146. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) UpdateNodeStatus(opts *bind.TransactOpts, _orgId string, _enodeId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateNodeStatus", _orgId, _enodeId, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x0cc50146. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) UpdateNodeStatus(_orgId string, _enodeId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNodeStatus(&_PermInterface.TransactOpts, _orgId, _enodeId, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x0cc50146. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) UpdateNodeStatus(_orgId string, _enodeId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNodeStatus(&_PermInterface.TransactOpts, _orgId, _enodeId, _action) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0xbb3b6e80. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) UpdateOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateOrgStatus", _orgId, _action) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0xbb3b6e80. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) UpdateOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0xbb3b6e80. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) UpdateOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} diff --git a/permission/v1/bind/permission_upgr.go b/permission/v1/bind/permission_upgr.go new file mode 100644 index 0000000000..6f8de04912 --- /dev/null +++ b/permission/v1/bind/permission_upgr.go @@ -0,0 +1,313 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PermUpgrABI is the input ABI used to generate the binding from. +const PermUpgrABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"getPermImpl\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_proposedImpl\",\"type\":\"address\"}],\"name\":\"confirmImplChange\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getGuardian\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getPermInterface\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_permInterface\",\"type\":\"address\"},{\"name\":\"_permImpl\",\"type\":\"address\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_guardian\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" + +var PermUpgrParsedABI, _ = abi.JSON(strings.NewReader(PermUpgrABI)) + +// PermUpgrBin is the compiled bytecode used for deploying new contracts. +var PermUpgrBin = "0x608060405234801561001057600080fd5b506040516020806106e78339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905560028054600160a01b60ff0219169055610675806100726000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e32cf901461005c57806322bcb39a14610080578063a75b87d2146100a8578063e572515c146100b0578063f09a4016146100b8575b600080fd5b6100646100e6565b604080516001600160a01b039092168252519081900360200190f35b6100a66004803603602081101561009657600080fd5b50356001600160a01b03166100f5565b005b61006461030b565b61006461031a565b6100a6600480360360408110156100ce57600080fd5b506001600160a01b0381358116916020013516610329565b6001546001600160a01b031690565b6000546001600160a01b0316331461014b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60608060606000600160009054906101000a90046001600160a01b03166001600160a01b031663cc9ba6fa6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a057600080fd5b505afa1580156101b4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156101dd57600080fd5b8101908080516401000000008111156101f557600080fd5b8201602081018481111561020857600080fd5b815164010000000081118282018710171561022257600080fd5b5050929190602001805164010000000081111561023e57600080fd5b8201602081018481111561025157600080fd5b815164010000000081118282018710171561026b57600080fd5b5050929190602001805164010000000081111561028757600080fd5b8201602081018481111561029a57600080fd5b81516401000000008111828201871017156102b457600080fd5b50506020909101519498509296509194509192506102d9915086905085858585610443565b600180546001600160a01b0319166001600160a01b03878116919091179182905561030491166105e4565b5050505050565b6000546001600160a01b031690565b6002546001600160a01b031690565b6000546001600160a01b0316331461037f5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600254600160a01b900460ff16156103e15760408051600160e51b62461bcd02815260206004820152601960248201527f63616e206265206578656375746564206f6e6c79206f6e636500000000000000604482015290519081900360640190fd5b600180546001600160a01b038084166001600160a01b031992831617928390556002805486831693169290921790915561041b91166105e4565b50506002805474ff00000000000000000000000000000000000000001916600160a01b179055565b846001600160a01b031663f5ad584a858585856040518563ffffffff1660e01b81526004018080602001806020018060200185151515158152602001848103845288818151815260200191508051906020019080838360005b838110156104b457818101518382015260200161049c565b50505050905090810190601f1680156104e15780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156105145781810151838201526020016104fc565b50505050905090810190601f1680156105415780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b8381101561057457818101518382015260200161055c565b50505050905090810190601f1680156105a15780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156105c557600080fd5b505af11580156105d9573d6000803e3d6000fd5b505050505050505050565b60025460408051600160e01b63511bbd9f0281526001600160a01b0384811660048301529151919092169163511bbd9f91602480830192600092919082900301818387803b15801561063557600080fd5b505af1158015610304573d6000803e3d6000fdfea165627a7a72305820baed98682426c4ca5713954d4aec5ce8f78637bbf627bd8f53ff37aac2394a950029" + +// DeployPermUpgr deploys a new Ethereum contract, binding an instance of PermUpgr to it. +func DeployPermUpgr(auth *bind.TransactOpts, backend bind.ContractBackend, _guardian common.Address) (common.Address, *types.Transaction, *PermUpgr, error) { + parsed, err := abi.JSON(strings.NewReader(PermUpgrABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PermUpgrBin), backend, _guardian) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &PermUpgr{PermUpgrCaller: PermUpgrCaller{contract: contract}, PermUpgrTransactor: PermUpgrTransactor{contract: contract}, PermUpgrFilterer: PermUpgrFilterer{contract: contract}}, nil +} + +// PermUpgr is an auto generated Go binding around an Ethereum contract. +type PermUpgr struct { + PermUpgrCaller // Read-only binding to the contract + PermUpgrTransactor // Write-only binding to the contract + PermUpgrFilterer // Log filterer for contract events +} + +// PermUpgrCaller is an auto generated read-only Go binding around an Ethereum contract. +type PermUpgrCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermUpgrTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PermUpgrTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermUpgrFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PermUpgrFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermUpgrSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PermUpgrSession struct { + Contract *PermUpgr // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermUpgrCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PermUpgrCallerSession struct { + Contract *PermUpgrCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PermUpgrTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PermUpgrTransactorSession struct { + Contract *PermUpgrTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermUpgrRaw is an auto generated low-level Go binding around an Ethereum contract. +type PermUpgrRaw struct { + Contract *PermUpgr // Generic contract binding to access the raw methods on +} + +// PermUpgrCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PermUpgrCallerRaw struct { + Contract *PermUpgrCaller // Generic read-only contract binding to access the raw methods on +} + +// PermUpgrTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PermUpgrTransactorRaw struct { + Contract *PermUpgrTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPermUpgr creates a new instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgr(address common.Address, backend bind.ContractBackend) (*PermUpgr, error) { + contract, err := bindPermUpgr(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PermUpgr{PermUpgrCaller: PermUpgrCaller{contract: contract}, PermUpgrTransactor: PermUpgrTransactor{contract: contract}, PermUpgrFilterer: PermUpgrFilterer{contract: contract}}, nil +} + +// NewPermUpgrCaller creates a new read-only instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgrCaller(address common.Address, caller bind.ContractCaller) (*PermUpgrCaller, error) { + contract, err := bindPermUpgr(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PermUpgrCaller{contract: contract}, nil +} + +// NewPermUpgrTransactor creates a new write-only instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgrTransactor(address common.Address, transactor bind.ContractTransactor) (*PermUpgrTransactor, error) { + contract, err := bindPermUpgr(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PermUpgrTransactor{contract: contract}, nil +} + +// NewPermUpgrFilterer creates a new log filterer instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgrFilterer(address common.Address, filterer bind.ContractFilterer) (*PermUpgrFilterer, error) { + contract, err := bindPermUpgr(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PermUpgrFilterer{contract: contract}, nil +} + +// bindPermUpgr binds a generic wrapper to an already deployed contract. +func bindPermUpgr(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PermUpgrABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermUpgr *PermUpgrRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermUpgr.Contract.PermUpgrCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermUpgr *PermUpgrRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermUpgr.Contract.PermUpgrTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermUpgr *PermUpgrRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermUpgr.Contract.PermUpgrTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermUpgr *PermUpgrCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermUpgr.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermUpgr *PermUpgrTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermUpgr.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermUpgr *PermUpgrTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermUpgr.Contract.contract.Transact(opts, method, params...) +} + +// GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. +// +// Solidity: function getGuardian() constant returns(address) +func (_PermUpgr *PermUpgrCaller) GetGuardian(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermUpgr.contract.Call(opts, out, "getGuardian") + return *ret0, err +} + +// GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. +// +// Solidity: function getGuardian() constant returns(address) +func (_PermUpgr *PermUpgrSession) GetGuardian() (common.Address, error) { + return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) +} + +// GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. +// +// Solidity: function getGuardian() constant returns(address) +func (_PermUpgr *PermUpgrCallerSession) GetGuardian() (common.Address, error) { + return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) +} + +// GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. +// +// Solidity: function getPermImpl() constant returns(address) +func (_PermUpgr *PermUpgrCaller) GetPermImpl(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermUpgr.contract.Call(opts, out, "getPermImpl") + return *ret0, err +} + +// GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. +// +// Solidity: function getPermImpl() constant returns(address) +func (_PermUpgr *PermUpgrSession) GetPermImpl() (common.Address, error) { + return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) +} + +// GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. +// +// Solidity: function getPermImpl() constant returns(address) +func (_PermUpgr *PermUpgrCallerSession) GetPermImpl() (common.Address, error) { + return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) +} + +// GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. +// +// Solidity: function getPermInterface() constant returns(address) +func (_PermUpgr *PermUpgrCaller) GetPermInterface(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermUpgr.contract.Call(opts, out, "getPermInterface") + return *ret0, err +} + +// GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. +// +// Solidity: function getPermInterface() constant returns(address) +func (_PermUpgr *PermUpgrSession) GetPermInterface() (common.Address, error) { + return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) +} + +// GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. +// +// Solidity: function getPermInterface() constant returns(address) +func (_PermUpgr *PermUpgrCallerSession) GetPermInterface() (common.Address, error) { + return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) +} + +// ConfirmImplChange is a paid mutator transaction binding the contract method 0x22bcb39a. +// +// Solidity: function confirmImplChange(address _proposedImpl) returns() +func (_PermUpgr *PermUpgrTransactor) ConfirmImplChange(opts *bind.TransactOpts, _proposedImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.contract.Transact(opts, "confirmImplChange", _proposedImpl) +} + +// ConfirmImplChange is a paid mutator transaction binding the contract method 0x22bcb39a. +// +// Solidity: function confirmImplChange(address _proposedImpl) returns() +func (_PermUpgr *PermUpgrSession) ConfirmImplChange(_proposedImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.ConfirmImplChange(&_PermUpgr.TransactOpts, _proposedImpl) +} + +// ConfirmImplChange is a paid mutator transaction binding the contract method 0x22bcb39a. +// +// Solidity: function confirmImplChange(address _proposedImpl) returns() +func (_PermUpgr *PermUpgrTransactorSession) ConfirmImplChange(_proposedImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.ConfirmImplChange(&_PermUpgr.TransactOpts, _proposedImpl) +} + +// Init is a paid mutator transaction binding the contract method 0xf09a4016. +// +// Solidity: function init(address _permInterface, address _permImpl) returns() +func (_PermUpgr *PermUpgrTransactor) Init(opts *bind.TransactOpts, _permInterface common.Address, _permImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.contract.Transact(opts, "init", _permInterface, _permImpl) +} + +// Init is a paid mutator transaction binding the contract method 0xf09a4016. +// +// Solidity: function init(address _permInterface, address _permImpl) returns() +func (_PermUpgr *PermUpgrSession) Init(_permInterface common.Address, _permImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.Init(&_PermUpgr.TransactOpts, _permInterface, _permImpl) +} + +// Init is a paid mutator transaction binding the contract method 0xf09a4016. +// +// Solidity: function init(address _permInterface, address _permImpl) returns() +func (_PermUpgr *PermUpgrTransactorSession) Init(_permInterface common.Address, _permImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.Init(&_PermUpgr.TransactOpts, _permInterface, _permImpl) +} diff --git a/permission/v1/bind/roles.go b/permission/v1/bind/roles.go new file mode 100644 index 0000000000..1e05bbe3e7 --- /dev/null +++ b/permission/v1/bind/roles.go @@ -0,0 +1,718 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// RoleManagerABI is the input ABI used to generate the binding from. +const RoleManagerABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getRoleDetails\",\"outputs\":[{\"name\":\"roleId\",\"type\":\"string\"},{\"name\":\"orgId\",\"type\":\"string\"},{\"name\":\"accessType\",\"type\":\"uint256\"},{\"name\":\"voter\",\"type\":\"bool\"},{\"name\":\"admin\",\"type\":\"bool\"},{\"name\":\"active\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_baseAccess\",\"type\":\"uint256\"},{\"name\":\"_isVoter\",\"type\":\"bool\"},{\"name\":\"_isAdmin\",\"type\":\"bool\"}],\"name\":\"addRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfRoles\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_rIndex\",\"type\":\"uint256\"}],\"name\":\"getRoleDetailsFromIndex\",\"outputs\":[{\"name\":\"roleId\",\"type\":\"string\"},{\"name\":\"orgId\",\"type\":\"string\"},{\"name\":\"accessType\",\"type\":\"uint256\"},{\"name\":\"voter\",\"type\":\"bool\"},{\"name\":\"admin\",\"type\":\"bool\"},{\"name\":\"active\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"removeRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"roleExists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"isAdminRole\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"isVoterRole\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_baseAccess\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_isVoter\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_isAdmin\",\"type\":\"bool\"}],\"name\":\"RoleCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"}]" + +var RoleManagerParsedABI, _ = abi.JSON(strings.NewReader(RoleManagerABI)) + +// RoleManagerBin is the compiled bytecode used for deploying new contracts. +var RoleManagerBin = "0x608060405234801561001057600080fd5b506040516020806122418339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556121df806100626000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063a63430121161005b578063a6343012146103ba578063abf5739f14610478578063be322e541461063a578063deb16ba71461074857610088565b80631870aba31461008d5780637b7135791461024957806387f55d3114610383578063a451d4a81461039d575b600080fd5b61014b600480360360408110156100a357600080fd5b810190602081018135600160201b8111156100bd57600080fd5b8201836020820111156100cf57600080fd5b803590602001918460018302840111600160201b831117156100f057600080fd5b919390929091602081019035600160201b81111561010d57600080fd5b82018360208201111561011f57600080fd5b803590602001918460018302840111600160201b8311171561014057600080fd5b509092509050610856565b604080519081018590528315156060820152821515608082015281151560a082015260c08082528751908201528651819060208083019160e08401918b019080838360005b838110156101a8578181015183820152602001610190565b50505050905090810190601f1680156101d55780820380516001836020036101000a031916815260200191505b5083810382528851815288516020918201918a019080838360005b838110156102085781810151838201526020016101f0565b50505050905090810190601f1680156102355780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610381600480360360a081101561025f57600080fd5b810190602081018135600160201b81111561027957600080fd5b82018360208201111561028b57600080fd5b803590602001918460018302840111600160201b831117156102ac57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460018302840111600160201b8311171561033157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550508235935050506020810135151590604001351515610bdc565b005b61038b611182565b60408051918252519081900360200190f35b61014b600480360360208110156103b357600080fd5b5035611189565b610381600480360360408110156103d057600080fd5b810190602081018135600160201b8111156103ea57600080fd5b8201836020820111156103fc57600080fd5b803590602001918460018302840111600160201b8311171561041d57600080fd5b919390929091602081019035600160201b81111561043a57600080fd5b82018360208201111561044c57600080fd5b803590602001918460018302840111600160201b8311171561046d57600080fd5b5090925090506113a7565b6106266004803603606081101561048e57600080fd5b810190602081018135600160201b8111156104a857600080fd5b8201836020820111156104ba57600080fd5b803590602001918460018302840111600160201b831117156104db57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561052d57600080fd5b82018360208201111561053f57600080fd5b803590602001918460018302840111600160201b8311171561056057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156105b257600080fd5b8201836020820111156105c457600080fd5b803590602001918460018302840111600160201b831117156105e557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506116a2945050505050565b604080519115158252519081900360200190f35b6106266004803603606081101561065057600080fd5b810190602081018135600160201b81111561066a57600080fd5b82018360208201111561067c57600080fd5b803590602001918460018302840111600160201b8311171561069d57600080fd5b919390929091602081019035600160201b8111156106ba57600080fd5b8201836020820111156106cc57600080fd5b803590602001918460018302840111600160201b831117156106ed57600080fd5b919390929091602081019035600160201b81111561070a57600080fd5b82018360208201111561071c57600080fd5b803590602001918460018302840111600160201b8311171561073d57600080fd5b509092509050611916565b6106266004803603606081101561075e57600080fd5b810190602081018135600160201b81111561077857600080fd5b82018360208201111561078a57600080fd5b803590602001918460018302840111600160201b831117156107ab57600080fd5b919390929091602081019035600160201b8111156107c857600080fd5b8201836020820111156107da57600080fd5b803590602001918460018302840111600160201b831117156107fb57600080fd5b919390929091602081019035600160201b81111561081857600080fd5b82018360208201111561082a57600080fd5b803590602001918460018302840111600160201b8311171561084b57600080fd5b509092509050611c96565b6060806000806000806108e08a8a8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8e018190048102820181019092528c815292508c91508b90819084018382808284376000920182905250604080516020810190915290815292506116a2915050565b151561094a57898960008060008085858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506040805160208101909152908152939f50929d50959b509399509197509550610bcf945050505050565b60006109bf8b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8f018190048102820181019092528d815292508d91508c908190840183828082843760009201919091525061200b92505050565b90506001818154811015156109d057fe5b90600052602060002090600402016000016001828154811015156109f057fe5b9060005260206000209060040201600101600183815481101515610a1057fe5b906000526020600020906004020160020154600184815481101515610a3157fe5b60009182526020909120600360049092020101546001805460ff9092169186908110610a5957fe5b906000526020600020906004020160030160019054906101000a900460ff16600186815481101515610a8757fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff169290918891830182828015610b2c5780601f10610b0157610100808354040283529160200191610b2c565b820191906000526020600020905b815481529060010190602001808311610b0f57829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a945092508401905082828015610bba5780601f10610b8f57610100808354040283529160200191610bba565b820191906000526020600020905b815481529060010190602001808311610b9d57829003601f168201915b50505050509450965096509650965096509650505b9499939850945094509450565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610c2957600080fd5b505afa158015610c3d573d6000803e3d6000fd5b505050506040513d6020811015610c5357600080fd5b50516001600160a01b03163314610ca85760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60048310610d005760408051600160e51b62461bcd02815260206004820152601460248201527f696e76616c6964206163636573732076616c7565000000000000000000000000604482015290519081900360640190fd5b600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015610d4a578181015183820152602001610d32565b50505050905090810190601f168015610d775780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015610daa578181015183820152602001610d92565b50505050905090810190601f168015610dd75780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515610e5c5760408051600160e51b62461bcd02815260206004820152601760248201527f726f6c652065786973747320666f7220746865206f7267000000000000000000604482015290519081900360640190fd5b60038054600101908190556040805160208082018381528951606084015289516002946000948c948c94938493830192608001918701908083838b5b83811015610eb0578181015183820152602001610e98565b50505050905090810190601f168015610edd5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015610f10578181015183820152602001610ef8565b50505050905090810190601f168015610f3d5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208852878201989098529587016000908120989098555050845160c0810186528b81528085018b905294850189905250505084151560608301528315156080830152600160a083018190528054808201808355919094528251805191946004027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60192610fe79284929091019061211b565b506020828101518051611000926001850192019061211b565b5060408281015160028301556060808401516003909301805460808087015160a09788015160ff199093169615159690961761ff001916610100961515969096029590951762ff0000191662010000911515919091021790558151918201889052861515908201528415159181019190915281815287519181019190915286517fefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c92508791879187918791879190819060208083019160c08401918a019080838360005b838110156110dc5781810151838201526020016110c4565b50505050905090810190601f1680156111095780820380516001836020036101000a031916815260200191505b50838103825287518152875160209182019189019080838360005b8381101561113c578181015183820152602001611124565b50505050905090810190601f1680156111695780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15050505050565b6001545b90565b6060806000806000806001878154811015156111a157fe5b90600052602060002090600402016000016001888154811015156111c157fe5b90600052602060002090600402016001016001898154811015156111e157fe5b90600052602060002090600402016002015460018a81548110151561120257fe5b60009182526020909120600360049092020101546001805460ff909216918c90811061122a57fe5b906000526020600020906004020160030160019054906101000a900460ff1660018c81548110151561125857fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff1692909188918301828280156112fd5780601f106112d2576101008083540402835291602001916112fd565b820191906000526020600020905b8154815290600101906020018083116112e057829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a94509250840190508282801561138b5780601f106113605761010080835404028352916020019161138b565b820191906000526020600020905b81548152906001019060200180831161136e57829003601f168201915b5050505050945095509550955095509550955091939550919395565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156113f457600080fd5b505afa158015611408573d6000803e3d6000fd5b505050506040513d602081101561141e57600080fd5b50516001600160a01b031633146114735760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000858585856040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f82011690508083019250505096505050505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415151561155f5760408051600160e51b62461bcd02815260206004820152601360248201527f726f6c6520646f6573206e6f7420657869737400000000000000000000000000604482015290519081900360640190fd5b60006115d485858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525061200b92505050565b905060006001828154811015156115e757fe5b906000526020600020906004020160030160026101000a81548160ff0219169083151502179055507f1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b600080600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156116ef5781810151838201526020016116d7565b50505050905090810190601f16801561171c5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561174f578181015183820152602001611737565b50505050905090810190601f16801561177c5780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415156117f3576117bb858561200b565b90506001818154811015156117cc57fe5b906000526020600020906004020160030160029054906101000a900460ff1691505061190f565b600260008685604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561183d578181015183820152602001611825565b50505050905090810190601f16801561186a5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561189d578181015183820152602001611885565b50505050905090810190601f1680156118ca5780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611909576117bb858461200b565b60009150505b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561196557600080fd5b505afa158015611979573d6000803e3d6000fd5b505050506040513d602081101561198f57600080fd5b50516001600160a01b031633146119e45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611a8b87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506116a292505050565b1515611a9957506000611c8c565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611bb057611ba988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a915089908190840183828082843760009201919091525061200b92505050565b9050611c26565b611c2388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061200b92505050565b90505b6001805482908110611c3457fe5b906000526020600020906004020160030160029054906101000a900460ff168015611c8857506001805482908110611c6857fe5b906000526020600020906004020160030160019054906101000a900460ff165b9150505b9695505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611ce557600080fd5b505afa158015611cf9573d6000803e3d6000fd5b505050506040513d6020811015611d0f57600080fd5b50516001600160a01b03163314611d645760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611e0b87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506116a292505050565b1515611e1957506000611c8c565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611f3057611f2988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a915089908190840183828082843760009201919091525061200b92505050565b9050611fa6565b611fa388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061200b92505050565b90505b6001805482908110611fb457fe5b906000526020600020906004020160030160029054906101000a900460ff168015611c8857506001805482908110611fe857fe5b600091825260209091206004909102016003015460ff1698975050505050505050565b60006001600260008585604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015612059578181015183820152602001612041565b50505050905090810190601f1680156120865780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156120b95781810151838201526020016120a1565b50505050905090810190601f1680156120e65780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205403905092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061215c57805160ff1916838001178555612189565b82800160010185558215612189579182015b8281111561218957825182559160200191906001019061216e565b50612195929150612199565b5090565b61118691905b80821115612195576000815560010161219f56fea165627a7a723058209059a9af47943da0750b529cb5cf17b9f0745cfb3bea00dad68345c815bbec800029" + +// DeployRoleManager deploys a new Ethereum contract, binding an instance of RoleManager to it. +func DeployRoleManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *RoleManager, error) { + parsed, err := abi.JSON(strings.NewReader(RoleManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(RoleManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &RoleManager{RoleManagerCaller: RoleManagerCaller{contract: contract}, RoleManagerTransactor: RoleManagerTransactor{contract: contract}, RoleManagerFilterer: RoleManagerFilterer{contract: contract}}, nil +} + +// RoleManager is an auto generated Go binding around an Ethereum contract. +type RoleManager struct { + RoleManagerCaller // Read-only binding to the contract + RoleManagerTransactor // Write-only binding to the contract + RoleManagerFilterer // Log filterer for contract events +} + +// RoleManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type RoleManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RoleManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type RoleManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RoleManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type RoleManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RoleManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type RoleManagerSession struct { + Contract *RoleManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RoleManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type RoleManagerCallerSession struct { + Contract *RoleManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// RoleManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type RoleManagerTransactorSession struct { + Contract *RoleManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RoleManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type RoleManagerRaw struct { + Contract *RoleManager // Generic contract binding to access the raw methods on +} + +// RoleManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type RoleManagerCallerRaw struct { + Contract *RoleManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// RoleManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type RoleManagerTransactorRaw struct { + Contract *RoleManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewRoleManager creates a new instance of RoleManager, bound to a specific deployed contract. +func NewRoleManager(address common.Address, backend bind.ContractBackend) (*RoleManager, error) { + contract, err := bindRoleManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &RoleManager{RoleManagerCaller: RoleManagerCaller{contract: contract}, RoleManagerTransactor: RoleManagerTransactor{contract: contract}, RoleManagerFilterer: RoleManagerFilterer{contract: contract}}, nil +} + +// NewRoleManagerCaller creates a new read-only instance of RoleManager, bound to a specific deployed contract. +func NewRoleManagerCaller(address common.Address, caller bind.ContractCaller) (*RoleManagerCaller, error) { + contract, err := bindRoleManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &RoleManagerCaller{contract: contract}, nil +} + +// NewRoleManagerTransactor creates a new write-only instance of RoleManager, bound to a specific deployed contract. +func NewRoleManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*RoleManagerTransactor, error) { + contract, err := bindRoleManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &RoleManagerTransactor{contract: contract}, nil +} + +// NewRoleManagerFilterer creates a new log filterer instance of RoleManager, bound to a specific deployed contract. +func NewRoleManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*RoleManagerFilterer, error) { + contract, err := bindRoleManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &RoleManagerFilterer{contract: contract}, nil +} + +// bindRoleManager binds a generic wrapper to an already deployed contract. +func bindRoleManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(RoleManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_RoleManager *RoleManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _RoleManager.Contract.RoleManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_RoleManager *RoleManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _RoleManager.Contract.RoleManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_RoleManager *RoleManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _RoleManager.Contract.RoleManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_RoleManager *RoleManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _RoleManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_RoleManager *RoleManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _RoleManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_RoleManager *RoleManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _RoleManager.Contract.contract.Transact(opts, method, params...) +} + +// GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. +// +// Solidity: function getNumberOfRoles() constant returns(uint256) +func (_RoleManager *RoleManagerCaller) GetNumberOfRoles(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "getNumberOfRoles") + return *ret0, err +} + +// GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. +// +// Solidity: function getNumberOfRoles() constant returns(uint256) +func (_RoleManager *RoleManagerSession) GetNumberOfRoles() (*big.Int, error) { + return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) +} + +// GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. +// +// Solidity: function getNumberOfRoles() constant returns(uint256) +func (_RoleManager *RoleManagerCallerSession) GetNumberOfRoles() (*big.Int, error) { + return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) +} + +// GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. +// +// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + ret := new(struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool + }) + out := ret + err := _RoleManager.contract.Call(opts, out, "getRoleDetails", _roleId, _orgId) + return *ret, err +} + +// GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. +// +// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerSession) GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetails(&_RoleManager.CallOpts, _roleId, _orgId) +} + +// GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. +// +// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCallerSession) GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetails(&_RoleManager.CallOpts, _roleId, _orgId) +} + +// GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. +// +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOpts, _rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + ret := new(struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool + }) + out := ret + err := _RoleManager.contract.Call(opts, out, "getRoleDetailsFromIndex", _rIndex) + return *ret, err +} + +// GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. +// +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetailsFromIndex(&_RoleManager.CallOpts, _rIndex) +} + +// GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. +// +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCallerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetailsFromIndex(&_RoleManager.CallOpts, _rIndex) +} + +// IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. +// +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCaller) IsAdminRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "isAdminRole", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. +// +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. +// +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. +// +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCaller) IsVoterRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "isVoterRole", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. +// +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. +// +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// RoleExists is a free data retrieval call binding the contract method 0xabf5739f. +// +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCaller) RoleExists(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "roleExists", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// RoleExists is a free data retrieval call binding the contract method 0xabf5739f. +// +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// RoleExists is a free data retrieval call binding the contract method 0xabf5739f. +// +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// AddRole is a paid mutator transaction binding the contract method 0x7b713579. +// +// Solidity: function addRole(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) returns() +func (_RoleManager *RoleManagerTransactor) AddRole(opts *bind.TransactOpts, _roleId string, _orgId string, _baseAccess *big.Int, _isVoter bool, _isAdmin bool) (*types.Transaction, error) { + return _RoleManager.contract.Transact(opts, "addRole", _roleId, _orgId, _baseAccess, _isVoter, _isAdmin) +} + +// AddRole is a paid mutator transaction binding the contract method 0x7b713579. +// +// Solidity: function addRole(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) returns() +func (_RoleManager *RoleManagerSession) AddRole(_roleId string, _orgId string, _baseAccess *big.Int, _isVoter bool, _isAdmin bool) (*types.Transaction, error) { + return _RoleManager.Contract.AddRole(&_RoleManager.TransactOpts, _roleId, _orgId, _baseAccess, _isVoter, _isAdmin) +} + +// AddRole is a paid mutator transaction binding the contract method 0x7b713579. +// +// Solidity: function addRole(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) returns() +func (_RoleManager *RoleManagerTransactorSession) AddRole(_roleId string, _orgId string, _baseAccess *big.Int, _isVoter bool, _isAdmin bool) (*types.Transaction, error) { + return _RoleManager.Contract.AddRole(&_RoleManager.TransactOpts, _roleId, _orgId, _baseAccess, _isVoter, _isAdmin) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_RoleManager *RoleManagerTransactor) RemoveRole(opts *bind.TransactOpts, _roleId string, _orgId string) (*types.Transaction, error) { + return _RoleManager.contract.Transact(opts, "removeRole", _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_RoleManager *RoleManagerSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _RoleManager.Contract.RemoveRole(&_RoleManager.TransactOpts, _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_RoleManager *RoleManagerTransactorSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _RoleManager.Contract.RemoveRole(&_RoleManager.TransactOpts, _roleId, _orgId) +} + +// RoleManagerRoleCreatedIterator is returned from FilterRoleCreated and is used to iterate over the raw logs and unpacked data for RoleCreated events raised by the RoleManager contract. +type RoleManagerRoleCreatedIterator struct { + Event *RoleManagerRoleCreated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *RoleManagerRoleCreatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleCreated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleCreated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *RoleManagerRoleCreatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *RoleManagerRoleCreatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// RoleManagerRoleCreated represents a RoleCreated event raised by the RoleManager contract. +type RoleManagerRoleCreated struct { + RoleId string + OrgId string + BaseAccess *big.Int + IsVoter bool + IsAdmin bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleCreated is a free log retrieval operation binding the contract event 0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c. +// +// Solidity: event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) +func (_RoleManager *RoleManagerFilterer) FilterRoleCreated(opts *bind.FilterOpts) (*RoleManagerRoleCreatedIterator, error) { + + logs, sub, err := _RoleManager.contract.FilterLogs(opts, "RoleCreated") + if err != nil { + return nil, err + } + return &RoleManagerRoleCreatedIterator{contract: _RoleManager.contract, event: "RoleCreated", logs: logs, sub: sub}, nil +} + +var RoleCreatedTopicHash = "0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c" + +// WatchRoleCreated is a free log subscription operation binding the contract event 0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c. +// +// Solidity: event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) +func (_RoleManager *RoleManagerFilterer) WatchRoleCreated(opts *bind.WatchOpts, sink chan<- *RoleManagerRoleCreated) (event.Subscription, error) { + + logs, sub, err := _RoleManager.contract.WatchLogs(opts, "RoleCreated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(RoleManagerRoleCreated) + if err := _RoleManager.contract.UnpackLog(event, "RoleCreated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleCreated is a log parse operation binding the contract event 0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c. +// +// Solidity: event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) +func (_RoleManager *RoleManagerFilterer) ParseRoleCreated(log types.Log) (*RoleManagerRoleCreated, error) { + event := new(RoleManagerRoleCreated) + if err := _RoleManager.contract.UnpackLog(event, "RoleCreated", log); err != nil { + return nil, err + } + return event, nil +} + +// RoleManagerRoleRevokedIterator is returned from FilterRoleRevoked and is used to iterate over the raw logs and unpacked data for RoleRevoked events raised by the RoleManager contract. +type RoleManagerRoleRevokedIterator struct { + Event *RoleManagerRoleRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *RoleManagerRoleRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *RoleManagerRoleRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *RoleManagerRoleRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// RoleManagerRoleRevoked represents a RoleRevoked event raised by the RoleManager contract. +type RoleManagerRoleRevoked struct { + RoleId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleRevoked is a free log retrieval operation binding the contract event 0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6. +// +// Solidity: event RoleRevoked(string _roleId, string _orgId) +func (_RoleManager *RoleManagerFilterer) FilterRoleRevoked(opts *bind.FilterOpts) (*RoleManagerRoleRevokedIterator, error) { + + logs, sub, err := _RoleManager.contract.FilterLogs(opts, "RoleRevoked") + if err != nil { + return nil, err + } + return &RoleManagerRoleRevokedIterator{contract: _RoleManager.contract, event: "RoleRevoked", logs: logs, sub: sub}, nil +} + +var RoleRevokedTopicHash = "0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6" + +// WatchRoleRevoked is a free log subscription operation binding the contract event 0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6. +// +// Solidity: event RoleRevoked(string _roleId, string _orgId) +func (_RoleManager *RoleManagerFilterer) WatchRoleRevoked(opts *bind.WatchOpts, sink chan<- *RoleManagerRoleRevoked) (event.Subscription, error) { + + logs, sub, err := _RoleManager.contract.WatchLogs(opts, "RoleRevoked") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(RoleManagerRoleRevoked) + if err := _RoleManager.contract.UnpackLog(event, "RoleRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleRevoked is a log parse operation binding the contract event 0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6. +// +// Solidity: event RoleRevoked(string _roleId, string _orgId) +func (_RoleManager *RoleManagerFilterer) ParseRoleRevoked(log types.Log) (*RoleManagerRoleRevoked, error) { + event := new(RoleManagerRoleRevoked) + if err := _RoleManager.contract.UnpackLog(event, "RoleRevoked", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v1/bind/voter.go b/permission/v1/bind/voter.go new file mode 100644 index 0000000000..8c5835206d --- /dev/null +++ b/permission/v1/bind/voter.go @@ -0,0 +1,853 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// VoterManagerABI is the input ABI used to generate the binding from. +const VoterManagerABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getPendingOpDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"addVoter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"deleteVoter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_authOrg\",\"type\":\"string\"},{\"name\":\"_vAccount\",\"type\":\"address\"},{\"name\":\"_pendingOp\",\"type\":\"uint256\"}],\"name\":\"processVote\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_authOrg\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_pendingOp\",\"type\":\"uint256\"}],\"name\":\"addVotingItem\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"VoterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"VoterDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"VotingItemAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"VoteProcessed\",\"type\":\"event\"}]" + +var VoterManagerParsedABI, _ = abi.JSON(strings.NewReader(VoterManagerABI)) + +// VoterManagerBin is the compiled bytecode used for deploying new contracts. +var VoterManagerBin = "0x6080604052600060035534801561001557600080fd5b50604051602080611fe48339810180604052602081101561003557600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055611f7d806100676000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063014e6acc1461005c5780635607395b146101c857806359cbd6fe14610241578063b0213864146102b8578063e98ac22d14610349575b600080fd5b6100ca6004803603602081101561007257600080fd5b810190602081018135600160201b81111561008c57600080fd5b82018360208201111561009e57600080fd5b803590602001918460018302840111600160201b831117156100bf57600080fd5b509092509050610466565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015610129578181015183820152602001610111565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015610189578181015183820152602001610171565b50505050905090810190601f1680156101b65780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61023f600480360360408110156101de57600080fd5b810190602081018135600160201b8111156101f857600080fd5b82018360208201111561020a57600080fd5b803590602001918460018302840111600160201b8311171561022b57600080fd5b9193509150356001600160a01b0316610740565b005b61023f6004803603604081101561025757600080fd5b810190602081018135600160201b81111561027157600080fd5b82018360208201111561028357600080fd5b803590602001918460018302840111600160201b831117156102a457600080fd5b9193509150356001600160a01b0316610f06565b610335600480360360608110156102ce57600080fd5b810190602081018135600160201b8111156102e857600080fd5b8201836020820111156102fa57600080fd5b803590602001918460018302840111600160201b8311171561031b57600080fd5b91935091506001600160a01b0381351690602001356111e8565b604080519115158252519081900360200190f35b61023f600480360360a081101561035f57600080fd5b810190602081018135600160201b81111561037957600080fd5b82018360208201111561038b57600080fd5b803590602001918460018302840111600160201b831117156103ac57600080fd5b919390929091602081019035600160201b8111156103c957600080fd5b8201836020820111156103db57600080fd5b803590602001918460018302840111600160201b831117156103fc57600080fd5b919390929091602081019035600160201b81111561041957600080fd5b82018360208201111561042b57600080fd5b803590602001918460018302840111600160201b8311171561044c57600080fd5b91935091506001600160a01b0381351690602001356116f8565b6060806000806000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156104b957600080fd5b505afa1580156104cd573d6000803e3d6000fd5b505050506040513d60208110156104e357600080fd5b50516001600160a01b031633146105385760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600061057987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561058a57fe5b90600052602060002090600b02016004016000016001828154811015156105ad57fe5b90600052602060002090600b02016004016001016001838154811015156105d057fe5b600091825260209091206006600b909202010154600180546001600160a01b0390921691859081106105fe57fe5b60009182526020918290206007600b909202010154845460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928691908301828280156106995780601f1061066e57610100808354040283529160200191610699565b820191906000526020600020905b81548152906001019060200180831161067c57829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156107275780601f106106fc57610100808354040283529160200191610727565b820191906000526020600020905b81548152906001019060200180831161070a57829003601f168201915b5050505050925094509450945094505092959194509250565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561078d57600080fd5b505afa1580156107a1573d6000803e3d6000fd5b505050506040513d60208110156107b757600080fd5b50516001600160a01b0316331461080c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000848460405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415610b78576003805460010190819055604080516020808201908152918101859052600291600091879187918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506000600180548091906001016109069190611cdd565b9050838360018381548110151561091957fe5b6000918252602090912061093393600b9092020191611d0e565b506001808281548110151561094457fe5b90600052602060002090600b0201600101819055506001808281548110151561096957fe5b90600052602060002090600b020160020181905550600060018281548110151561098f57fe5b90600052602060002090600b020160030181905550604051806020016040528060008152506001828154811015156109c357fe5b90600052602060002090600b020160040160000190805190602001906109ea929190611d8c565b506040805160208101909152600081526001805483908110610a0857fe5b90600052602060002090600b02016004016001019080519060200190610a2f929190611d8c565b506000600182815481101515610a4157fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b0393909316929092179091556001805483908110610a8157fe5b600091825260209091206007600b9092020101556001805482908110610aa357fe5b90600052602060002090600b020160010154600182815481101515610ac457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610afb57fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b969096029093016008018054938401815586529290942093519301805492516001600160a01b03199093169390911692909217600160a01b60ff021916600160a01b9115159190910217905550610e90565b6000610bb984848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050600181815481101515610bca57fe5b600091825260208083206001600160a01b03861684526009600b9093020191909101905260409020541515610d2c576001805482908110610c0757fe5b600091825260209091206001600b909202018101805482019055805482908110610c2d57fe5b90600052602060002090600b020160010154600182815481101515610c4e57fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610c8557fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b96909602909301600801805480850182559087529390952090519201805493516001600160a01b03199094169290941691909117600160a01b60ff021916600160a01b9215159290920291909117909155805482908110610d0d57fe5b600091825260209091206002600b909202010180546001019055610e8e565b6000610d6f85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250879250611ba5915050565b9050600182815481101515610d8057fe5b90600052602060002090600b020160080181815481101515610d9e57fe5b600091825260209091200154600160a01b900460ff16151560011415610e0e5760408051600160e51b62461bcd02815260206004820152600f60248201527f616c7265616479206120766f7465720000000000000000000000000000000000604482015290519081900360640190fd5b60018083815481101515610e1e57fe5b90600052602060002090600b020160080182815481101515610e3c57fe5b60009182526020909120018054911515600160a01b02600160a01b60ff02199092169190911790556001805483908110610e7257fe5b600091825260209091206002600b909202010180546001019055505b505b604080516001600160a01b03831660208201528181529081018390527f424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574908490849084908060608101858580828437600083820152604051601f909101601f1916909201829003965090945050505050a1505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610f5357600080fd5b505afa158015610f67573d6000803e3d6000fd5b505050506040513d6020811015610f7d57600080fd5b50516001600160a01b03163314610fd25760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250849250611016915083905082611bf7565b15156001146110645760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b60006110a586868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060006110ea87878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250899250611ba5915050565b90506001828154811015156110fb57fe5b6000918252602082206002600b90920201018054600019019055600180548490811061112357fe5b90600052602060002090600b02016008018281548110151561114157fe5b9060005260206000200160000160146101000a81548160ff0219169083151502179055507f654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b68787876040518080602001836001600160a01b03166001600160a01b031681526020018281038252858582818152602001925080828437600083820152604051601f909101601f1916909201829003965090945050505050a150505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561123757600080fd5b505afa15801561124b573d6000803e3d6000fd5b505050506040513d602081101561126157600080fd5b50516001600160a01b031633146112b65760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508692506112fa915083905082611bf7565b15156001146113485760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b61138987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250889250611ca7915050565b15156001146113e25760408051600160e51b62461bcd02815260206004820152601260248201527f6e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b600061142388888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561143457fe5b60009182526020808320848452600a600b9093020191909101815260408083206001600160a01b038a16845290915290205460ff161515600114156114c35760408051600160e51b62461bcd02815260206004820152601260248201527f63616e6e6f7420646f75626c6520766f74650000000000000000000000000000604482015290519081900360640190fd5b60018054829081106114d157fe5b600091825260209091206003600b90920201018054600190810190915580548190839081106114fc57fe5b60009182526020808320858452600b92909202909101600a01815260408083206001600160a01b038b168452825291829020805460ff19169315159390931790925580518281529182018990527f87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508918a918a919081908101848480828437600083820152604051601f909101601f19169092018290039550909350505050a160026001828154811015156115ac57fe5b90600052602060002090600b0201600201548115156115c757fe5b046001828154811015156115d757fe5b90600052602060002090600b02016003015411156116e857604080516020810190915260008152600180548390811061160c57fe5b90600052602060002090600b02016004016000019080519060200190611633929190611d8c565b50604080516020810190915260008152600180548390811061165157fe5b90600052602060002090600b02016004016001019080519060200190611678929190611d8c565b50600060018281548110151561168a57fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b03939093169290921790915560018054839081106116ca57fe5b600091825260209091206007600b90920201015550600192506116ee565b60009350505b5050949350505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561174557600080fd5b505afa158015611759573d6000803e3d6000fd5b505050506040513d602081101561176f57600080fd5b50516001600160a01b031633146117c45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b61180388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509250611ca7915050565b151561184357604051600160e51b62461bcd028152600401808060200182810382526034815260200180611f1e6034913960400191505060405180910390fd5b600061188489898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050868660018381548110151561189757fe5b90600052602060002090600b020160040160000191906118b8929190611d0e565b5084846001838154811015156118ca57fe5b90600052602060002090600b020160040160010191906118eb929190611d0e565b50826001828154811015156118fc57fe5b90600052602060002090600b020160040160020160006101000a8154816001600160a01b0302191690836001600160a01b031602179055508160018281548110151561194457fe5b6000918252602082206007600b9092020101919091555b600180548390811061196957fe5b90600052602060002090600b020160080180549050811015611a6b57600180548390811061199357fe5b90600052602060002090600b0201600801818154811015156119b157fe5b600091825260209091200154600160a01b900460ff1615611a635760006001838154811015156119dd57fe5b90600052602060002090600b0201600a0160008481526020019081526020016000206000600185815481101515611a1057fe5b90600052602060002090600b020160080184815481101515611a2e57fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff19169115159190911790555b60010161195b565b506000600182815481101515611a7d57fe5b90600052602060002090600b0201600301819055507f5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3898960405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a1505050505050505050565b6000600160026000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611b46578181015183820152602001611b2e565b50505050905090810190601f168015611b735780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b600080611bb184611afd565b905060018082815481101515611bc357fe5b600091825260208083206001600160a01b03881684526009600b909302019190910190526040902054039150505b92915050565b600080611c0384611afd565b9050600181815481101515611c1457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020541515611c4d576000915050611bf1565b6000611c598585611ba5565b9050600182815481101515611c6a57fe5b90600052602060002090600b020160080181815481101515611c8857fe5b600091825260209091200154600160a01b900460ff1695945050505050565b6000816001611cb585611afd565b81548110611cbf57fe5b90600052602060002090600b02016004016003015414905092915050565b815481835581811115611d0957600b0281600b028360005260206000209182019101611d099190611dfa565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611d4f5782800160ff19823516178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578235825591602001919060010190611d61565b50611d88929150611e7f565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611dcd57805160ff1916838001178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578251825591602001919060010190611ddf565b611e7c91905b80821115611d88576000611e148282611e99565b60006001830181905560028301819055600383018190556004830190611e3a8282611e99565b611e48600183016000611e99565b506002810180546001600160a01b031916905560006003909101819055611e73906008840190611ee0565b50600b01611e00565b90565b611e7c91905b80821115611d885760008155600101611e85565b50805460018160011615610100020316600290046000825580601f10611ebf5750611edd565b601f016020900490600052602060002090810190611edd9190611e7f565b50565b5080546000825590600052602060002090810190611edd9190611e7c91905b80821115611d885780546001600160a81b0319168155600101611eff56fe6974656d732070656e64696e6720666f7220617070726f76616c2e206e6577206974656d2063616e6e6f74206265206164646564a165627a7a723058208c26d91675a875844a1887c5ed36a158ac303cb35eb5df6294460bcd07c299780029" + +// DeployVoterManager deploys a new Ethereum contract, binding an instance of VoterManager to it. +func DeployVoterManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *VoterManager, error) { + parsed, err := abi.JSON(strings.NewReader(VoterManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(VoterManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &VoterManager{VoterManagerCaller: VoterManagerCaller{contract: contract}, VoterManagerTransactor: VoterManagerTransactor{contract: contract}, VoterManagerFilterer: VoterManagerFilterer{contract: contract}}, nil +} + +// VoterManager is an auto generated Go binding around an Ethereum contract. +type VoterManager struct { + VoterManagerCaller // Read-only binding to the contract + VoterManagerTransactor // Write-only binding to the contract + VoterManagerFilterer // Log filterer for contract events +} + +// VoterManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type VoterManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VoterManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type VoterManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VoterManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type VoterManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VoterManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type VoterManagerSession struct { + Contract *VoterManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// VoterManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type VoterManagerCallerSession struct { + Contract *VoterManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// VoterManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type VoterManagerTransactorSession struct { + Contract *VoterManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// VoterManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type VoterManagerRaw struct { + Contract *VoterManager // Generic contract binding to access the raw methods on +} + +// VoterManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type VoterManagerCallerRaw struct { + Contract *VoterManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// VoterManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type VoterManagerTransactorRaw struct { + Contract *VoterManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewVoterManager creates a new instance of VoterManager, bound to a specific deployed contract. +func NewVoterManager(address common.Address, backend bind.ContractBackend) (*VoterManager, error) { + contract, err := bindVoterManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &VoterManager{VoterManagerCaller: VoterManagerCaller{contract: contract}, VoterManagerTransactor: VoterManagerTransactor{contract: contract}, VoterManagerFilterer: VoterManagerFilterer{contract: contract}}, nil +} + +// NewVoterManagerCaller creates a new read-only instance of VoterManager, bound to a specific deployed contract. +func NewVoterManagerCaller(address common.Address, caller bind.ContractCaller) (*VoterManagerCaller, error) { + contract, err := bindVoterManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VoterManagerCaller{contract: contract}, nil +} + +// NewVoterManagerTransactor creates a new write-only instance of VoterManager, bound to a specific deployed contract. +func NewVoterManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*VoterManagerTransactor, error) { + contract, err := bindVoterManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VoterManagerTransactor{contract: contract}, nil +} + +// NewVoterManagerFilterer creates a new log filterer instance of VoterManager, bound to a specific deployed contract. +func NewVoterManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*VoterManagerFilterer, error) { + contract, err := bindVoterManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VoterManagerFilterer{contract: contract}, nil +} + +// bindVoterManager binds a generic wrapper to an already deployed contract. +func bindVoterManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(VoterManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_VoterManager *VoterManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _VoterManager.Contract.VoterManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_VoterManager *VoterManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VoterManager.Contract.VoterManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_VoterManager *VoterManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VoterManager.Contract.VoterManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_VoterManager *VoterManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _VoterManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_VoterManager *VoterManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VoterManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_VoterManager *VoterManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VoterManager.Contract.contract.Transact(opts, method, params...) +} + +// GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. +// +// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +func (_VoterManager *VoterManagerCaller) GetPendingOpDetails(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(common.Address) + ret3 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _VoterManager.contract.Call(opts, out, "getPendingOpDetails", _orgId) + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. +// +// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +func (_VoterManager *VoterManagerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { + return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) +} + +// GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. +// +// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +func (_VoterManager *VoterManagerCallerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { + return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) +} + +// AddVoter is a paid mutator transaction binding the contract method 0x5607395b. +// +// Solidity: function addVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactor) AddVoter(opts *bind.TransactOpts, _orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "addVoter", _orgId, _vAccount) +} + +// AddVoter is a paid mutator transaction binding the contract method 0x5607395b. +// +// Solidity: function addVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerSession) AddVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.AddVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// AddVoter is a paid mutator transaction binding the contract method 0x5607395b. +// +// Solidity: function addVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactorSession) AddVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.AddVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// AddVotingItem is a paid mutator transaction binding the contract method 0xe98ac22d. +// +// Solidity: function addVotingItem(string _authOrg, string _orgId, string _enodeId, address _account, uint256 _pendingOp) returns() +func (_VoterManager *VoterManagerTransactor) AddVotingItem(opts *bind.TransactOpts, _authOrg string, _orgId string, _enodeId string, _account common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "addVotingItem", _authOrg, _orgId, _enodeId, _account, _pendingOp) +} + +// AddVotingItem is a paid mutator transaction binding the contract method 0xe98ac22d. +// +// Solidity: function addVotingItem(string _authOrg, string _orgId, string _enodeId, address _account, uint256 _pendingOp) returns() +func (_VoterManager *VoterManagerSession) AddVotingItem(_authOrg string, _orgId string, _enodeId string, _account common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.AddVotingItem(&_VoterManager.TransactOpts, _authOrg, _orgId, _enodeId, _account, _pendingOp) +} + +// AddVotingItem is a paid mutator transaction binding the contract method 0xe98ac22d. +// +// Solidity: function addVotingItem(string _authOrg, string _orgId, string _enodeId, address _account, uint256 _pendingOp) returns() +func (_VoterManager *VoterManagerTransactorSession) AddVotingItem(_authOrg string, _orgId string, _enodeId string, _account common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.AddVotingItem(&_VoterManager.TransactOpts, _authOrg, _orgId, _enodeId, _account, _pendingOp) +} + +// DeleteVoter is a paid mutator transaction binding the contract method 0x59cbd6fe. +// +// Solidity: function deleteVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactor) DeleteVoter(opts *bind.TransactOpts, _orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "deleteVoter", _orgId, _vAccount) +} + +// DeleteVoter is a paid mutator transaction binding the contract method 0x59cbd6fe. +// +// Solidity: function deleteVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerSession) DeleteVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.DeleteVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// DeleteVoter is a paid mutator transaction binding the contract method 0x59cbd6fe. +// +// Solidity: function deleteVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactorSession) DeleteVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.DeleteVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// ProcessVote is a paid mutator transaction binding the contract method 0xb0213864. +// +// Solidity: function processVote(string _authOrg, address _vAccount, uint256 _pendingOp) returns(bool) +func (_VoterManager *VoterManagerTransactor) ProcessVote(opts *bind.TransactOpts, _authOrg string, _vAccount common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "processVote", _authOrg, _vAccount, _pendingOp) +} + +// ProcessVote is a paid mutator transaction binding the contract method 0xb0213864. +// +// Solidity: function processVote(string _authOrg, address _vAccount, uint256 _pendingOp) returns(bool) +func (_VoterManager *VoterManagerSession) ProcessVote(_authOrg string, _vAccount common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.ProcessVote(&_VoterManager.TransactOpts, _authOrg, _vAccount, _pendingOp) +} + +// ProcessVote is a paid mutator transaction binding the contract method 0xb0213864. +// +// Solidity: function processVote(string _authOrg, address _vAccount, uint256 _pendingOp) returns(bool) +func (_VoterManager *VoterManagerTransactorSession) ProcessVote(_authOrg string, _vAccount common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.ProcessVote(&_VoterManager.TransactOpts, _authOrg, _vAccount, _pendingOp) +} + +// VoterManagerVoteProcessedIterator is returned from FilterVoteProcessed and is used to iterate over the raw logs and unpacked data for VoteProcessed events raised by the VoterManager contract. +type VoterManagerVoteProcessedIterator struct { + Event *VoterManagerVoteProcessed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVoteProcessedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoteProcessed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoteProcessed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVoteProcessedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVoteProcessedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVoteProcessed represents a VoteProcessed event raised by the VoterManager contract. +type VoterManagerVoteProcessed struct { + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVoteProcessed is a free log retrieval operation binding the contract event 0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508. +// +// Solidity: event VoteProcessed(string _orgId) +func (_VoterManager *VoterManagerFilterer) FilterVoteProcessed(opts *bind.FilterOpts) (*VoterManagerVoteProcessedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VoteProcessed") + if err != nil { + return nil, err + } + return &VoterManagerVoteProcessedIterator{contract: _VoterManager.contract, event: "VoteProcessed", logs: logs, sub: sub}, nil +} + +var VoteProcessedTopicHash = "0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508" + +// WatchVoteProcessed is a free log subscription operation binding the contract event 0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508. +// +// Solidity: event VoteProcessed(string _orgId) +func (_VoterManager *VoterManagerFilterer) WatchVoteProcessed(opts *bind.WatchOpts, sink chan<- *VoterManagerVoteProcessed) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VoteProcessed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVoteProcessed) + if err := _VoterManager.contract.UnpackLog(event, "VoteProcessed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVoteProcessed is a log parse operation binding the contract event 0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508. +// +// Solidity: event VoteProcessed(string _orgId) +func (_VoterManager *VoterManagerFilterer) ParseVoteProcessed(log types.Log) (*VoterManagerVoteProcessed, error) { + event := new(VoterManagerVoteProcessed) + if err := _VoterManager.contract.UnpackLog(event, "VoteProcessed", log); err != nil { + return nil, err + } + return event, nil +} + +// VoterManagerVoterAddedIterator is returned from FilterVoterAdded and is used to iterate over the raw logs and unpacked data for VoterAdded events raised by the VoterManager contract. +type VoterManagerVoterAddedIterator struct { + Event *VoterManagerVoterAdded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVoterAddedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVoterAddedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVoterAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVoterAdded represents a VoterAdded event raised by the VoterManager contract. +type VoterManagerVoterAdded struct { + OrgId string + VAccount common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVoterAdded is a free log retrieval operation binding the contract event 0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574. +// +// Solidity: event VoterAdded(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) FilterVoterAdded(opts *bind.FilterOpts) (*VoterManagerVoterAddedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VoterAdded") + if err != nil { + return nil, err + } + return &VoterManagerVoterAddedIterator{contract: _VoterManager.contract, event: "VoterAdded", logs: logs, sub: sub}, nil +} + +var VoterAddedTopicHash = "0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574" + +// WatchVoterAdded is a free log subscription operation binding the contract event 0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574. +// +// Solidity: event VoterAdded(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) WatchVoterAdded(opts *bind.WatchOpts, sink chan<- *VoterManagerVoterAdded) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VoterAdded") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVoterAdded) + if err := _VoterManager.contract.UnpackLog(event, "VoterAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVoterAdded is a log parse operation binding the contract event 0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574. +// +// Solidity: event VoterAdded(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) ParseVoterAdded(log types.Log) (*VoterManagerVoterAdded, error) { + event := new(VoterManagerVoterAdded) + if err := _VoterManager.contract.UnpackLog(event, "VoterAdded", log); err != nil { + return nil, err + } + return event, nil +} + +// VoterManagerVoterDeletedIterator is returned from FilterVoterDeleted and is used to iterate over the raw logs and unpacked data for VoterDeleted events raised by the VoterManager contract. +type VoterManagerVoterDeletedIterator struct { + Event *VoterManagerVoterDeleted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVoterDeletedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterDeleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterDeleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVoterDeletedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVoterDeletedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVoterDeleted represents a VoterDeleted event raised by the VoterManager contract. +type VoterManagerVoterDeleted struct { + OrgId string + VAccount common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVoterDeleted is a free log retrieval operation binding the contract event 0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6. +// +// Solidity: event VoterDeleted(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) FilterVoterDeleted(opts *bind.FilterOpts) (*VoterManagerVoterDeletedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VoterDeleted") + if err != nil { + return nil, err + } + return &VoterManagerVoterDeletedIterator{contract: _VoterManager.contract, event: "VoterDeleted", logs: logs, sub: sub}, nil +} + +var VoterDeletedTopicHash = "0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6" + +// WatchVoterDeleted is a free log subscription operation binding the contract event 0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6. +// +// Solidity: event VoterDeleted(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) WatchVoterDeleted(opts *bind.WatchOpts, sink chan<- *VoterManagerVoterDeleted) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VoterDeleted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVoterDeleted) + if err := _VoterManager.contract.UnpackLog(event, "VoterDeleted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVoterDeleted is a log parse operation binding the contract event 0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6. +// +// Solidity: event VoterDeleted(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) ParseVoterDeleted(log types.Log) (*VoterManagerVoterDeleted, error) { + event := new(VoterManagerVoterDeleted) + if err := _VoterManager.contract.UnpackLog(event, "VoterDeleted", log); err != nil { + return nil, err + } + return event, nil +} + +// VoterManagerVotingItemAddedIterator is returned from FilterVotingItemAdded and is used to iterate over the raw logs and unpacked data for VotingItemAdded events raised by the VoterManager contract. +type VoterManagerVotingItemAddedIterator struct { + Event *VoterManagerVotingItemAdded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVotingItemAddedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVotingItemAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVotingItemAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVotingItemAddedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVotingItemAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVotingItemAdded represents a VotingItemAdded event raised by the VoterManager contract. +type VoterManagerVotingItemAdded struct { + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVotingItemAdded is a free log retrieval operation binding the contract event 0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3. +// +// Solidity: event VotingItemAdded(string _orgId) +func (_VoterManager *VoterManagerFilterer) FilterVotingItemAdded(opts *bind.FilterOpts) (*VoterManagerVotingItemAddedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VotingItemAdded") + if err != nil { + return nil, err + } + return &VoterManagerVotingItemAddedIterator{contract: _VoterManager.contract, event: "VotingItemAdded", logs: logs, sub: sub}, nil +} + +var VotingItemAddedTopicHash = "0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3" + +// WatchVotingItemAdded is a free log subscription operation binding the contract event 0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3. +// +// Solidity: event VotingItemAdded(string _orgId) +func (_VoterManager *VoterManagerFilterer) WatchVotingItemAdded(opts *bind.WatchOpts, sink chan<- *VoterManagerVotingItemAdded) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VotingItemAdded") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVotingItemAdded) + if err := _VoterManager.contract.UnpackLog(event, "VotingItemAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVotingItemAdded is a log parse operation binding the contract event 0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3. +// +// Solidity: event VotingItemAdded(string _orgId) +func (_VoterManager *VoterManagerFilterer) ParseVotingItemAdded(log types.Log) (*VoterManagerVotingItemAdded, error) { + event := new(VoterManagerVotingItemAdded) + if err := _VoterManager.contract.UnpackLog(event, "VotingItemAdded", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v1/contract.go b/permission/v1/contract.go new file mode 100644 index 0000000000..c27f30537c --- /dev/null +++ b/permission/v1/contract.go @@ -0,0 +1,372 @@ +package v1 + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" + pb "github.com/ethereum/go-ethereum/permission/v1/bind" +) + +type PermissionModelV1 struct { + ContractBackend ptype.ContractBackend + PermInterf *pb.PermInterface + PermInterfSession *pb.PermInterfaceSession +} + +type Audit struct { + Backend *PermissionModelV1 +} + +type Role struct { + Backend *PermissionModelV1 +} + +type Control struct { +} + +type Org struct { + Backend *PermissionModelV1 +} + +type Node struct { + Backend *PermissionModelV1 +} + +type Account struct { + Backend *PermissionModelV1 +} + +type Init struct { + Backend ptype.ContractBackend + + //pb contracts + PermUpgr *pb.PermUpgr + PermImpl *pb.PermImpl + PermInterf *pb.PermInterface + PermNode *pb.NodeManager + PermAcct *pb.AcctManager + PermRole *pb.RoleManager + PermOrg *pb.OrgManager + + //sessions + PermInterfSession *pb.PermInterfaceSession + permOrgSession *pb.OrgManagerSession + permNodeSession *pb.NodeManagerSession + permRoleSession *pb.RoleManagerSession + permAcctSession *pb.AcctManagerSession +} + +func (i *Init) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + return i.permAcctSession.GetAccountDetailsFromIndex(_aIndex) +} + +func (i *Init) GetNumberOfAccounts() (*big.Int, error) { + return i.permAcctSession.GetNumberOfAccounts() +} + +func (i *Init) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return i.permRoleSession.GetRoleDetailsFromIndex(_rIndex) +} + +func (i *Init) GetNumberOfRoles() (*big.Int, error) { + return i.permRoleSession.GetNumberOfRoles() +} + +func (i *Init) GetNumberOfOrgs() (*big.Int, error) { + return i.permOrgSession.GetNumberOfOrgs() +} + +func (i *Init) UpdateNetworkBootStatus() (*types.Transaction, error) { + return i.PermInterfSession.UpdateNetworkBootStatus() +} + +func (i *Init) AddAdminAccount(_acct common.Address) (*types.Transaction, error) { + return i.PermInterfSession.AddAdminAccount(_acct) +} + +func (i *Init) AddAdminNode(url string) (*types.Transaction, error) { + return i.PermInterfSession.AddAdminNode(url) +} + +func (i *Init) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return i.PermInterfSession.SetPolicy(_nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +func (i *Init) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return i.PermInterfSession.Init(_breadth, _depth) +} + +func (i *Init) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { + return i.permAcctSession.GetAccountDetails(_account) +} + +func (i *Init) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (string, string, *big.Int, error) { + r, err := i.permNodeSession.GetNodeDetailsFromIndex(_nodeIndex) + return r.OrgId, r.EnodeId, r.NodeStatus, err +} + +func (i *Init) GetNumberOfNodes() (*big.Int, error) { + return i.permNodeSession.GetNumberOfNodes() +} + +func (i *Init) GetNodeDetails(enodeId string) (string, string, *big.Int, error) { + r, err := i.permNodeSession.GetNodeDetails(enodeId) + return r.OrgId, r.EnodeId, r.NodeStatus, err +} + +func (i *Init) GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return i.permRoleSession.GetRoleDetails(_roleId, _orgId) +} + +func (i *Init) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { + return i.permOrgSession.GetSubOrgIndexes(_orgId) +} + +func (i *Init) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + return i.permOrgSession.GetOrgInfo(_orgIndex) +} + +func (i *Init) GetNetworkBootStatus() (bool, error) { + return i.PermInterfSession.GetNetworkBootStatus() +} + +func (i *Init) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { + return i.permOrgSession.GetOrgDetails(_orgId) +} + +func (a *Audit) ValidatePendingOp(_authOrg, _orgId, _url string, _account common.Address, _pendingOp int64) bool { + pOrg, pUrl, pAcct, op, err := a.Backend.PermInterfSession.GetPendingOp(_authOrg) + return err == nil && (op.Int64() == _pendingOp && pOrg == _orgId && pUrl == _url && pAcct == _account) +} + +func (a *Audit) CheckPendingOp(_orgId string) bool { + _, _, _, op, err := a.Backend.PermInterfSession.GetPendingOp(_orgId) + return err == nil && op.Int64() != 0 +} + +func (c *Control) ConnectionAllowed(_enodeId, _ip string, _port, _raftPort uint16) (bool, error) { + passedEnodeId, err := enode.ParseV4(_enodeId) + if err != nil { + return false, nil + } + nodeList := core.NodeInfoMap.GetNodeList() + for _, n := range nodeList { + recEnodeId, _ := enode.ParseV4(n.Url) + if recEnodeId.ID() == passedEnodeId.ID() && n.Status == core.NodeApproved { + return true, nil + } + } + return false, nil +} + +func (c *Control) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte, transactionType core.TransactionType) error { + accessType := core.GetAcctAccess(_sender) + switch accessType { + case core.ReadOnly: + return ptype.ErrNoPermissionForTxn + + case core.Transact: + if transactionType == core.ContractDeployTxn { + return ptype.ErrNoPermissionForTxn + } + return nil + + case core.FullAccess, core.ContractDeploy: + return nil + + default: + return ptype.ErrNoPermissionForTxn + + } +} + +func (r *Role) RemoveRole(_args ptype.TxArgs) (*types.Transaction, error) { + return r.Backend.PermInterfSession.RemoveRole(_args.RoleId, _args.OrgId) +} + +func (r *Role) AddNewRole(_args ptype.TxArgs) (*types.Transaction, error) { + if _args.AccessType > 3 { + return nil, fmt.Errorf("invalid access type given") + } + return r.Backend.PermInterfSession.AddNewRole(_args.RoleId, _args.OrgId, big.NewInt(int64(_args.AccessType)), _args.IsVoter, _args.IsAdmin) +} + +func (o *Org) ApproveOrgStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.ApproveOrgStatus(_args.OrgId, big.NewInt(int64(_args.Action))) +} + +func (o *Org) UpdateOrgStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.UpdateOrgStatus(_args.OrgId, big.NewInt(int64(_args.Action))) +} + +func (o *Org) ApproveOrg(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.ApproveOrg(_args.OrgId, _args.Url, _args.AcctId) +} + +func (o *Org) AddSubOrg(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.AddSubOrg(_args.POrgId, _args.OrgId, _args.Url) +} + +func (o *Org) AddOrg(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.AddOrg(_args.OrgId, _args.Url, _args.AcctId) +} + +func (n *Node) ApproveBlacklistedNodeRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + return n.Backend.PermInterfSession.ApproveBlacklistedNodeRecovery(_args.OrgId, _args.Url) +} + +func (n *Node) StartBlacklistedNodeRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + return n.Backend.PermInterfSession.StartBlacklistedNodeRecovery(_args.OrgId, _args.Url) +} + +func (n *Node) AddNode(_args ptype.TxArgs) (*types.Transaction, error) { + return n.Backend.PermInterfSession.AddNode(_args.OrgId, _args.Url) +} + +func (n *Node) UpdateNodeStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return n.Backend.PermInterfSession.UpdateNodeStatus(_args.OrgId, _args.Url, big.NewInt(int64(_args.Action))) +} + +func (a *Account) AssignAccountRole(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.AssignAccountRole(_args.AcctId, _args.OrgId, _args.RoleId) +} + +func (a *Account) UpdateAccountStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.UpdateAccountStatus(_args.OrgId, _args.AcctId, big.NewInt(int64(_args.Action))) +} + +func (a *Account) StartBlacklistedAccountRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.StartBlacklistedAccountRecovery(_args.OrgId, _args.AcctId) +} + +func (a *Account) ApproveBlacklistedAccountRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.ApproveBlacklistedAccountRecovery(_args.OrgId, _args.AcctId) +} + +func (a *Account) ApproveAdminRole(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.ApproveAdminRole(_args.OrgId, _args.AcctId) +} + +func (a *Account) AssignAdminRole(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.AssignAdminRole(_args.OrgId, _args.AcctId, _args.RoleId) +} + +// This is to make sure all Contr instances are ready and initialized +// +// Required to be call after standard service start lifecycle +func (i *Init) BindContracts() error { + log.Debug("permission service: binding contracts") + err := i.bindContract() + if err != nil { + return err + } + i.initSession() + return nil +} + +func (i *Init) bindContract() error { + if err := ptype.BindContract(&i.PermUpgr, func() (interface{}, error) { + return pb.NewPermUpgr(i.Backend.PermConfig.UpgrdAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermImpl, func() (interface{}, error) { + return pb.NewPermImpl(i.Backend.PermConfig.ImplAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermInterf, func() (interface{}, error) { + return pb.NewPermInterface(i.Backend.PermConfig.InterfAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermAcct, func() (interface{}, error) { + return pb.NewAcctManager(i.Backend.PermConfig.AccountAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermNode, func() (interface{}, error) { + return pb.NewNodeManager(i.Backend.PermConfig.NodeAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermRole, func() (interface{}, error) { + return pb.NewRoleManager(i.Backend.PermConfig.RoleAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermOrg, func() (interface{}, error) { + return pb.NewOrgManager(i.Backend.PermConfig.OrgAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + return nil +} + +func (i *Init) initSession() { + auth := bind.NewKeyedTransactor(i.Backend.Key) + log.Debug("NodeAccount V1", "nodeAcc", auth.From) + i.PermInterfSession = &pb.PermInterfaceSession{ + Contract: i.PermInterf, + CallOpts: bind.CallOpts{ + Pending: true, + }, + TransactOpts: bind.TransactOpts{ + From: auth.From, + Signer: auth.Signer, + GasLimit: 47000000, + GasPrice: big.NewInt(0), + }, + } + + i.permOrgSession = &pb.OrgManagerSession{ + Contract: i.PermOrg, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + + i.permNodeSession = &pb.NodeManagerSession{ + Contract: i.PermNode, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + + //populate roles + i.permRoleSession = &pb.RoleManagerSession{ + Contract: i.PermRole, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + + //populate accounts + i.permAcctSession = &pb.AcctManagerSession{ + Contract: i.PermAcct, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } +} diff --git a/permission/v1/contract/AccountManager.sol b/permission/v1/contract/AccountManager.sol new file mode 100644 index 0000000000..25bc7786ef --- /dev/null +++ b/permission/v1/contract/AccountManager.sol @@ -0,0 +1,362 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; + +/** @title Account manager contract + * @notice This contract holds implementation logic for all account management + functionality. This can be called only by the implementation contract only. + there are few view functions exposed as public and can be called directly. + these are invoked by quorum for populating permissions data in cache + * @dev account status is denoted by a fixed integer value. The values are + as below: + 0 - Not in list + 1 - Account pending approval + 2 - Active + 3 - Inactive + 4 - Suspended + 5 - Blacklisted + 6 - Revoked + 7 - Recovery Initiated for blacklisted accounts and pending approval + from network admins + Once the account is blacklisted no further activity on the account is + possible. + When adding a new org admin account to an existing org, the existing org + admin account will be in revoked status and can be assigned a new role + later + */ +contract AccountManager { + PermissionsUpgradable private permUpgradable; + struct AccountAccessDetails { + address account; + string orgId; + string role; + uint status; + bool orgAdmin; + } + + AccountAccessDetails[] private accountAccessList; + mapping(address => uint) private accountIndex; + uint private numAccounts; + + string private adminRole; + string private orgAdminRole; + + mapping(bytes32 => address) private orgAdminIndex; + + // account permission events + event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint _status); + event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin); + event AccountStatusChanged(address _account, string _orgId, uint _status); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if the account is exists and belongs to the org id passed + * @param _orgId - org id + * @param _account - account id + */ + modifier accountExists(string memory _orgId, address _account) { + require((accountIndex[_account]) != 0, "account does not exists"); + require(keccak256(abi.encode(accountAccessList[_getAccountIndex(_account)].orgId)) == keccak256(abi.encode(_orgId)), "account in different org"); + _; + } + + /// @notice constructor. sets the permissions upgradable address + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + + /** @notice returns the account details for a given account + * @param _account account id + * @return account id + * @return org id of the account + * @return role linked to the account + * @return status of the account + * @return bool indicating if the account is an org admin + */ + function getAccountDetails(address _account) external view returns (address, + string memory, string memory, uint, bool){ + if (accountIndex[_account] == 0) { + return (_account, "NONE", "", 0, false); + } + uint aIndex = _getAccountIndex(_account); + return (accountAccessList[aIndex].account, accountAccessList[aIndex].orgId, + accountAccessList[aIndex].role, accountAccessList[aIndex].status, + accountAccessList[aIndex].orgAdmin); + } + + /** @notice returns the account details a given account index + * @param _aIndex account index + * @return account id + * @return org id of the account + * @return role linked to the account + * @return status of the account + * @return bool indicating if the account is an org admin + */ + function getAccountDetailsFromIndex(uint _aIndex) external view returns + (address, string memory, string memory, uint, bool) { + return (accountAccessList[_aIndex].account, + accountAccessList[_aIndex].orgId, accountAccessList[_aIndex].role, + accountAccessList[_aIndex].status, accountAccessList[_aIndex].orgAdmin); + } + + /** @notice returns the total number of accounts + * @return total number accounts + */ + function getNumberOfAccounts() external view returns (uint) { + return accountAccessList.length; + } + + /** @notice this is called at the time of network initialization to set + the default values of network admin and org admin roles + */ + function setDefaults(string calldata _nwAdminRole, string calldata _oAdminRole) + external onlyImplementation { + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + } + + /** @notice this function is called to assign the org admin or network + admin roles only to the passed account + * @param _account - account id + * @param _orgId - org to which it belongs + * @param _roleId - role id to be assigned + * @param _status - account status to be assigned + */ + function assignAdminRole(address _account, string calldata _orgId, + string calldata _roleId, uint _status) external onlyImplementation { + require(((keccak256(abi.encode(_roleId)) == keccak256(abi.encode(orgAdminRole))) || + (keccak256(abi.encode(_roleId)) == keccak256(abi.encode(adminRole)))), + "can be called to assign admin roles only"); + + _setAccountRole(_account, _orgId, _roleId, _status, true); + + } + + /** @notice this function is called to assign the any role to the passed + account. + * @param _account - account id + * @param _orgId - org to which it belongs + * @param _roleId - role id to be assigned + * @param _adminRole - indicates of the role is an admin role + */ + function assignAccountRole(address _account, string calldata _orgId, + string calldata _roleId, bool _adminRole) external onlyImplementation { + require(((keccak256(abi.encode(_roleId)) != keccak256(abi.encode(adminRole))) + && (keccak256(abi.encode(abi.encode(_roleId))) != keccak256(abi.encode(orgAdminRole)))), + "cannot be called fro assigning org admin and network admin roles"); + _setAccountRole(_account, _orgId, _roleId, 2, _adminRole); + } + + /** @notice this function removes existing admin account. will be called at + the time of adding a new account as org admin account. at org + level there can be one org admin account only + * @param _orgId - org id + * @return bool to indicate if voter update is required or not + * @return _adminRole - indicates of the role is an admin role + */ + function removeExistingAdmin(string calldata _orgId) external + onlyImplementation + returns (bool voterUpdate, address account) { + // change the status of existing org admin to revoked + if (orgAdminExists(_orgId)) { + uint id = _getAccountIndex(orgAdminIndex[keccak256(abi.encode(_orgId))]); + accountAccessList[id].status = 6; + accountAccessList[id].orgAdmin = false; + emit AccountAccessModified(accountAccessList[id].account, + accountAccessList[id].orgId, accountAccessList[id].role, + accountAccessList[id].orgAdmin, accountAccessList[id].status); + return ((keccak256(abi.encode(accountAccessList[id].role)) == keccak256(abi.encode(adminRole))), + accountAccessList[id].account); + } + return (false, address(0)); + } + + /** @notice function to add an account as network admin or org admin. + * @param _orgId - org id + * @param _account - account id + * @return bool to indicate if voter update is required or not + */ + function addNewAdmin(string calldata _orgId, address _account) external + onlyImplementation + returns (bool voterUpdate) { + // check of the account role is org admin role and status is pending + // approval. if yes update the status to approved + string memory role = getAccountRole(_account); + uint status = _getAccountStatus(_account); + uint id = _getAccountIndex(_account); + if ((keccak256(abi.encode(role)) == keccak256(abi.encode(orgAdminRole))) && + (status == 1)) { + orgAdminIndex[keccak256(abi.encode(_orgId))] = _account; + } + accountAccessList[id].status = 2; + accountAccessList[id].orgAdmin = true; + emit AccountAccessModified(_account, accountAccessList[id].orgId, accountAccessList[id].role, + accountAccessList[id].orgAdmin, accountAccessList[id].status); + return (keccak256(abi.encode(accountAccessList[id].role)) == keccak256(abi.encode(adminRole))); + } + + /** @notice updates the account status to the passed status value + * @param _orgId - org id + * @param _account - account id + * @param _action - new status of the account + * @dev the following actions are allowed + 1 - Suspend the account + 2 - Reactivate a suspended account + 3 - Blacklist an account + 4 - Initiate recovery for black listed account + 5 - Complete recovery of black listed account and update status to active + */ + function updateAccountStatus(string calldata _orgId, address _account, uint _action) external + onlyImplementation + accountExists(_orgId, _account) { + require((_action > 0 && _action < 6), "invalid status change request"); + + // check if the account is org admin. if yes then do not allow any status change + require(checkOrgAdmin(_account, _orgId, "") != true, "status change not possible for org admin accounts"); + uint newStatus; + if (_action == 1) { + // for suspending an account current status should be active + require(accountAccessList[_getAccountIndex(_account)].status == 2, + "account is not in active status. operation cannot be done"); + newStatus = 4; + } + else if (_action == 2) { + // for reactivating a suspended account, current status should be suspended + require(accountAccessList[_getAccountIndex(_account)].status == 4, + "account is not in suspended status. operation cannot be done"); + newStatus = 2; + } + else if (_action == 3) { + require(accountAccessList[_getAccountIndex(_account)].status != 5, + "account is already blacklisted. operation cannot be done"); + newStatus = 5; + } + else if (_action == 4) { + require(accountAccessList[_getAccountIndex(_account)].status == 5, + "account is not blacklisted. operation cannot be done"); + newStatus = 7; + } + else if (_action == 5) { + require(accountAccessList[_getAccountIndex(_account)].status == 7, "account recovery not initiated. operation cannot be done"); + newStatus = 2; + } + + accountAccessList[_getAccountIndex(_account)].status = newStatus; + emit AccountStatusChanged(_account, _orgId, newStatus); + } + + /** @notice checks if the passed account exists and if exists does it + belong to the passed organization. + * @param _account - account id + * @param _orgId - org id + * @return bool true if the account does not exists or exists and belongs + * @return passed org + */ + function validateAccount(address _account, string calldata _orgId) external + view returns (bool){ + if (accountIndex[_account] == 0) { + return true; + } + uint256 id = _getAccountIndex(_account); + return (keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_orgId))); + } + + /** @notice checks if org admin account exists for the passed org id + * @param _orgId - org id + * @return true if the org admin account exists and is approved + */ + function orgAdminExists(string memory _orgId) public view returns (bool) { + if (orgAdminIndex[keccak256(abi.encode(_orgId))] != address(0)) { + address adminAcct = orgAdminIndex[keccak256(abi.encode(_orgId))]; + return _getAccountStatus(adminAcct) == 2; + } + return false; + + } + + /** @notice returns the role id linked to the passed account + * @param _account account id + * @return role id + */ + function getAccountRole(address _account) public view returns (string memory) { + if (accountIndex[_account] == 0) { + return "NONE"; + } + uint256 acctIndex = _getAccountIndex(_account); + if (accountAccessList[acctIndex].status != 0) { + return accountAccessList[acctIndex].role; + } + else { + return "NONE"; + } + } + + /** @notice checks if the account is a org admin for the passed org or + for the ultimate parent organization + * @param _account account id + * @param _orgId org id + * @param _ultParent master org id or + */ + function checkOrgAdmin(address _account, string memory _orgId, + string memory _ultParent) public view returns (bool) { + // check if the account role is network admin. If yes return success + if (keccak256(abi.encode(getAccountRole(_account))) == keccak256(abi.encode(adminRole))) { + // check of the orgid is network admin org. then return true + uint256 id = _getAccountIndex(_account); + return ((keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_orgId))) + || (keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_ultParent)))); + } + return ((orgAdminIndex[keccak256(abi.encode(_orgId))] == _account) || (orgAdminIndex[keccak256(abi.encode(_ultParent))] == _account)); + } + + /** @notice returns the index for a given account id + * @param _account account id + * @return account index + */ + function _getAccountIndex(address _account) internal view returns (uint256) { + return accountIndex[_account] - 1; + } + + /** @notice returns the account status for a given account + * @param _account account id + * @return account status + */ + function _getAccountStatus(address _account) internal view returns (uint256) { + if (accountIndex[_account] == 0) { + return 0; + } + uint256 aIndex = _getAccountIndex(_account); + return (accountAccessList[aIndex].status); + } + + /** @notice sets the account role to the passed role id and sets the status + * @param _account account id + * @param _orgId org id + * @param _status status to be set + * @param _oAdmin bool to indicate if account is org admin + */ + function _setAccountRole(address _account, string memory _orgId, + string memory _roleId, uint256 _status, bool _oAdmin) internal onlyImplementation { + // Check if account already exists + uint256 aIndex = _getAccountIndex(_account); + if (accountIndex[_account] != 0) { + accountAccessList[aIndex].role = _roleId; + accountAccessList[aIndex].status = _status; + accountAccessList[aIndex].orgAdmin = _oAdmin; + } + else { + numAccounts ++; + accountIndex[_account] = numAccounts; + accountAccessList.push(AccountAccessDetails(_account, _orgId, + _roleId, _status, _oAdmin)); + } + emit AccountAccessModified(_account, _orgId, _roleId, _oAdmin, _status); + } +} diff --git a/permission/v1/contract/NodeManager.sol b/permission/v1/contract/NodeManager.sol new file mode 100644 index 0000000000..62d3d12ac9 --- /dev/null +++ b/permission/v1/contract/NodeManager.sol @@ -0,0 +1,257 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; +/** @title Node manager contract + * @notice This contract holds implementation logic for all node management + functionality. This can be called only by the implementation contract. + There are few view functions exposed as public and can be called directly. + These are invoked by quorum for populating permissions data in cache + * @dev node status is denoted by a fixed integer value. The values are + as below: + 0 - Not in list + 1 - Node pending approval + 2 - Active + 3 - Deactivated + 4 - Blacklisted + 5 - Blacklisted node recovery initiated. Once approved the node + status will be updated to Active (2) + Once the node is blacklisted no further activity on the node is + possible. + */ +contract NodeManager { + PermissionsUpgradable private permUpgradable; + struct NodeDetails { + string enodeId; //e.g. 127.0.0.1:20005 + string orgId; + uint256 status; + } + // use an array to store node details + // if we want to list all node one day, mapping is not capable + NodeDetails[] private nodeList; + // mapping of enodeid to array index to track node + mapping(bytes32 => uint256) private nodeIdToIndex; + // tracking total number of nodes in network + uint256 private numberOfNodes; + + + // node permission events for new node propose + event NodeProposed(string _enodeId, string _orgId); + event NodeApproved(string _enodeId, string _orgId); + + // node permission events for node deactivation + event NodeDeactivated(string _enodeId, string _orgId); + + // node permission events for node activation + event NodeActivated(string _enodeId, string _orgId); + + // node permission events for node blacklist + event NodeBlacklisted(string _enodeId, string _orgId); + + // node permission events for initiating the recovery of blacklisted + // node + event NodeRecoveryInitiated(string _enodeId, string _orgId); + + // node permission events for completing the recovery of blacklisted + // node + event NodeRecoveryCompleted(string _enodeId, string _orgId); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if the node exists in the network + * @param _enodeId full enode id + */ + modifier enodeExists(string memory _enodeId) { + require(nodeIdToIndex[keccak256(abi.encode(_enodeId))] != 0, + "passed enode id does not exist"); + _; + } + + /** @notice checks if the node does not exist in the network + * @param _enodeId full enode id + */ + modifier enodeDoesNotExists(string memory _enodeId) { + require(nodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0, + "passed enode id exists"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice fetches the node details given an enode id + * @param _enodeId full enode id + * @return org id + * @return enode id + * @return status of the node + */ + function getNodeDetails(string calldata enodeId) external view + returns (string memory _orgId, string memory _enodeId, uint256 _nodeStatus) { + if (nodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0) { + return ("", enodeId, 0); + } + uint256 nodeIndex = _getNodeIndex(enodeId); + return (nodeList[nodeIndex].orgId, nodeList[nodeIndex].enodeId, + nodeList[nodeIndex].status); + } + + /** @notice fetches the node details given the index of the enode + * @param _nodeIndex node index + * @return org id + * @return enode id + * @return status of the node + */ + function getNodeDetailsFromIndex(uint256 _nodeIndex) external view + returns (string memory _orgId, string memory _enodeId, uint256 _nodeStatus) { + return (nodeList[_nodeIndex].orgId, nodeList[_nodeIndex].enodeId, + nodeList[_nodeIndex].status); + } + + /** @notice returns the total number of enodes in the network + * @return number of nodes + */ + function getNumberOfNodes() external view returns (uint256) { + return numberOfNodes; + } + + /** @notice called at the time of network initialization for adding + admin nodes + * @param _enodeId enode id + * @param _orgId org id to which the enode belongs + */ + function addAdminNode(string calldata _enodeId, string calldata _orgId) external + onlyImplementation + enodeDoesNotExists(_enodeId) { + numberOfNodes++; + nodeIdToIndex[keccak256(abi.encode(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _orgId, 2)); + emit NodeApproved(_enodeId, _orgId); + } + + /** @notice called at the time of new org creation to add node to org + * @param _enodeId enode id + * @param _orgId org id to which the enode belongs + */ + function addNode(string calldata _enodeId, string calldata _orgId) external + onlyImplementation + enodeDoesNotExists(_enodeId) { + numberOfNodes++; + nodeIdToIndex[keccak256(abi.encode(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _orgId, 1)); + emit NodeProposed(_enodeId, _orgId); + } + + /** @notice called org admins to add new enodes to the org or sub orgs + * @param _enodeId enode id + * @param _orgId org or sub org id to which the enode belongs + */ + function addOrgNode(string calldata _enodeId, string calldata _orgId) external + onlyImplementation + enodeDoesNotExists(_enodeId) { + numberOfNodes++; + nodeIdToIndex[keccak256(abi.encode(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _orgId, 2)); + emit NodeApproved(_enodeId, _orgId); + } + + /** @notice function to approve the node addition. only called at the time + master org creation by network admin + * @param _enodeId enode id + * @param _orgId org or sub org id to which the enode belongs + */ + function approveNode(string calldata _enodeId, string calldata _orgId) external + onlyImplementation + enodeExists(_enodeId) { + // node should belong to the passed org + require(_checkOrg(_enodeId, _orgId), "enode id does not belong to the passed org id"); + require(_getNodeStatus(_enodeId) == 1, "nothing pending for approval"); + uint256 nodeIndex = _getNodeIndex(_enodeId); + nodeList[nodeIndex].status = 2; + emit NodeApproved(nodeList[nodeIndex].enodeId, nodeList[nodeIndex].orgId); + } + + /** @notice updates the node status. can be called for deactivating/ + blacklisting and reactivating a deactivated node + * @param _enodeId enode id + * @param _orgId org or sub org id to which the enode belong + * @param _action action being performed + * @dev action can have any of the following values + 1 - Suspend the node + 2 - Revoke suspension of a suspended node + 3 - blacklist a node + 4 - initiate the recovery of a blacklisted node + 5 - blacklisted node recovery fully approved. mark to active + */ + function updateNodeStatus(string calldata _enodeId, string calldata _orgId, uint256 _action) external + onlyImplementation + enodeExists(_enodeId) { + // node should belong to the org + require(_checkOrg(_enodeId, _orgId), "enode id does not belong to the passed org"); + require((_action == 1 || _action == 2 || _action == 3 || _action == 4 || _action == 5), + "invalid operation. wrong action passed"); + + if (_action == 1) { + require(_getNodeStatus(_enodeId) == 2, "operation cannot be performed"); + nodeList[_getNodeIndex(_enodeId)].status = 3; + emit NodeDeactivated(_enodeId, _orgId); + } + else if (_action == 2) { + require(_getNodeStatus(_enodeId) == 3, "operation cannot be performed"); + nodeList[_getNodeIndex(_enodeId)].status = 2; + emit NodeActivated(_enodeId, _orgId); + } + else if (_action == 3) { + nodeList[_getNodeIndex(_enodeId)].status = 4; + emit NodeBlacklisted(_enodeId, _orgId); + } else if (_action == 4) { + // node should be in blacklisted state + require(_getNodeStatus(_enodeId) == 4, "operation cannot be performed"); + nodeList[_getNodeIndex(_enodeId)].status = 5; + emit NodeRecoveryInitiated(_enodeId, _orgId); + } else { + // node should be in initiated recovery state + require(_getNodeStatus(_enodeId) == 5, "operation cannot be performed"); + nodeList[_getNodeIndex(_enodeId)].status = 2; + emit NodeRecoveryCompleted(_enodeId, _orgId); + } + } + + // private functions + /** @notice returns the node index for given enode id + * @param _enodeId enode id + * @return trur or false + */ + function _getNodeIndex(string memory _enodeId) internal view + returns (uint256) { + return nodeIdToIndex[keccak256(abi.encode(_enodeId))] - 1; + } + + /** @notice checks if enode id is linked to the org id passed + * @param _enodeId enode id + * @param _orgId org or sub org id to which the enode belongs + * @return true or false + */ + function _checkOrg(string memory _enodeId, string memory _orgId) internal view + returns (bool) { + return (keccak256(abi.encode(nodeList[_getNodeIndex(_enodeId)].orgId)) == keccak256(abi.encode(_orgId))); + } + + /** @notice returns the node status for a given enode id + * @param _enodeId enode id + * @return node status + */ + function _getNodeStatus(string memory _enodeId) internal view returns (uint256) { + if (nodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0) { + return 0; + } + return nodeList[_getNodeIndex(_enodeId)].status; + } +} diff --git a/permission/v1/contract/OrgManager.sol b/permission/v1/contract/OrgManager.sol new file mode 100644 index 0000000000..d0b9a0b017 --- /dev/null +++ b/permission/v1/contract/OrgManager.sol @@ -0,0 +1,371 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; +/** @title Organization manager contract + * @notice This contract holds implementation logic for all org management + functionality. This can be called only by the implementation + contract only. there are few view functions exposed as public and + can be called directly. these are invoked by quorum for populating + permissions data in cache + * @dev the status of the organization is denoted by a set of integer + values. These are as below: + 0 - Not in list, + 1 - Org proposed for approval by network admins + 2 - Org in Approved status + 3 - Org proposed for suspension and pending approval by network admins + 4 - Org in Suspended, + Once the node is blacklisted no further activity on the node is + possible. + */ +contract OrgManager { + string private adminOrgId; + PermissionsUpgradable private permUpgradable; + // checks if first time network boot up has happened or not + bool private networkBoot = false; + + // variables which control the breadth and depth of the sub org tree + uint private DEPTH_LIMIT = 4; + uint private BREADTH_LIMIT = 4; + struct OrgDetails { + string orgId; + uint status; + string parentId; + string fullOrgId; + string ultParent; + uint pindex; + uint level; + uint [] subOrgIndexList; + } + + OrgDetails [] private orgList; + mapping(bytes32 => uint) private OrgIndex; + uint private orgNum = 0; + + // events related to Master Org add + event OrgApproved(string _orgId, string _porgId, string _ultParent, + uint _level, uint _status); + event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, + uint _level, uint _status); + event OrgSuspended(string _orgId, string _porgId, string _ultParent, + uint _level); + event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, + uint _level); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation{ + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if the org id does not exists + * @param _orgId - org id + * @return true if org does not exist + */ + modifier orgDoesNotExist(string memory _orgId) { + require(checkOrgExists(_orgId) == false, "org exists"); + _; + } + + /** @notice checks if the org id does exists + * @param _orgId - org id + * @return true if org exists + */ + modifier orgExists(string memory _orgId) { + require(checkOrgExists(_orgId) == true, "org does not exist"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice called at the time of network initialization. sets the depth + breadth for sub orgs creation. and creates the default network + admin org as per config file + */ + function setUpOrg(string calldata _orgId, uint256 _breadth, uint256 _depth) external + onlyImplementation { + _addNewOrg("", _orgId, 1, 2); + DEPTH_LIMIT = _depth; + BREADTH_LIMIT = _breadth; + } + /** @notice function for adding a new master org to the network + * @param _orgId unique org id to be added + * @dev org will be added if it does exist + */ + function addOrg(string calldata _orgId) external + onlyImplementation + orgDoesNotExist(_orgId) { + _addNewOrg("", _orgId, 1, 1); + } + + /** @notice function for adding a new sub org under a parent org + * @param _pOrgId unique org id to be added + * @dev org will be added if it does exist + */ + function addSubOrg(string calldata _pOrgId, string calldata _orgId) external + onlyImplementation + orgDoesNotExist(string(abi.encodePacked(_pOrgId, ".", _orgId))) { + _addNewOrg(_pOrgId, _orgId, 2, 2); + } + + /** @notice updates the status of a master org. + * @param _orgId unique org id to be added + * @param _action action being performed + * @dev status cannot be updated for sub orgs. + This function can be called for the following actions: + 1 - to suspend an org + 2 - to activate the org back + */ + function updateOrg(string calldata _orgId, uint256 _action) external + onlyImplementation + orgExists(_orgId) + returns (uint256){ + require((_action == 1 || _action == 2), "invalid action. operation not allowed"); + uint256 id = _getOrgIndex(_orgId); + require(orgList[id].level == 1, "not a master org. operation not allowed"); + + uint256 reqStatus; + uint256 pendingOp; + if (_action == 1) { + reqStatus = 2; + pendingOp = 2; + } + else if (_action == 2) { + reqStatus = 4; + pendingOp = 3; + } + require(checkOrgStatus(_orgId, reqStatus) == true, + "org status does not allow the operation"); + if (_action == 1) { + _suspendOrg(_orgId); + } + else { + _revokeOrgSuspension(_orgId); + } + return pendingOp; + } + + /** @notice function to approve org status change for master orgs + * @param _orgId unique org id to be added + * @param _action approval for action + * @dev status cannot be updated for sub orgs. + This function can be called for the following actions: + 1 - to suspend an org + 2 - to activate the org back + */ + function approveOrgStatusUpdate(string calldata _orgId, uint256 _action) external + onlyImplementation + orgExists(_orgId) { + if (_action == 1) { + _approveOrgSuspension(_orgId); + } + else { + _approveOrgRevokeSuspension(_orgId); + } + } + + /** @notice function to approve org status change for master orgs + * @param _orgId unique org id to be added + */ + function approveOrg(string calldata _orgId) external + onlyImplementation { + require(checkOrgStatus(_orgId, 1) == true, "nothing to approve"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 2; + emit OrgApproved(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 2); + } + + /** @notice returns org info for a given org index + * @param _orgIndex org index + * @return org id + * @return parent org id + * @return ultimate parent id + * @return level in the org tree + * @return status + */ + function getOrgInfo(uint256 _orgIndex) external view returns (string memory, + string memory, string memory, uint256, uint256) { + return (orgList[_orgIndex].orgId, orgList[_orgIndex].parentId, + orgList[_orgIndex].ultParent, orgList[_orgIndex].level, orgList[_orgIndex].status); + } + + /** @notice returns org info for a given org id + * @param _orgId org id + * @return org id + * @return parent org id + * @return ultimate parent id + * @return level in the org tree + * @return status + */ + function getOrgDetails(string calldata _orgId) external view returns (string memory, + string memory, string memory, uint256, uint256) { + if (!checkOrgExists(_orgId)){ + return (_orgId, "", "", 0,0); + } + uint256 _orgIndex = _getOrgIndex(_orgId); + return (orgList[_orgIndex].orgId, orgList[_orgIndex].parentId, + orgList[_orgIndex].ultParent, orgList[_orgIndex].level, orgList[_orgIndex].status); + } + + /** @notice returns the array of sub org indexes for the given org + * @param _orgId org id + * @return array of sub org indexes + */ + function getSubOrgIndexes(string calldata _orgId) external view returns (uint[] memory) { + require(checkOrgExists(_orgId) == true, "org does not exist"); + uint256 _orgIndex = _getOrgIndex(_orgId); + return (orgList[_orgIndex].subOrgIndexList); + } + /** @notice returns the master org id for the given org or sub org + * @param _orgId org id + * @return master org id + */ + function getUltimateParent(string calldata _orgId) external view + onlyImplementation + returns (string memory) { + return orgList[_getOrgIndex(_orgId)].ultParent; + } + + /** @notice returns the total number of orgs in the network + * @return master org id + */ + function getNumberOfOrgs() public view returns (uint256) { + return orgList.length; + } + + /** @notice confirms that org status is same as passed status + * @param _orgId org id + * @param _orgStatus org status + * @return true or false + */ + function checkOrgStatus(string memory _orgId, uint256 _orgStatus) + public view returns (bool){ + if (OrgIndex[keccak256(abi.encodePacked(_orgId))] == 0) { + return false; + } + uint256 id = _getOrgIndex(_orgId); + return ((OrgIndex[keccak256(abi.encodePacked(_orgId))] != 0) + && orgList[id].status == _orgStatus); + } + + /** @notice confirms if the org exists in the network + * @param _orgId org id + * @return true or false + */ + function checkOrgExists(string memory _orgId) public view returns (bool) { + return (!(OrgIndex[keccak256(abi.encodePacked(_orgId))] == 0)); + } + + /** @notice updates the org status to suspended + * @param _orgId org id + */ + function _suspendOrg(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 2) == true, + "org not in approved status. operation cannot be done"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 3; + emit OrgPendingApproval(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 3); + } + + /** @notice revokes the suspension of an org + * @param _orgId org id + */ + function _revokeOrgSuspension(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 4) == true, "org not in suspended state"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 5; + emit OrgPendingApproval(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 5); + } + + /** @notice approval function for org suspension activity + * @param _orgId org id + */ + function _approveOrgSuspension(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 3) == true, "nothing to approve"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 4; + emit OrgSuspended(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level); + } + + /** @notice approval function for revoking org suspension + * @param _orgId org id + */ + function _approveOrgRevokeSuspension(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 5) == true, "nothing to approve"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 2; + emit OrgSuspensionRevoked(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level); + } + + /** @notice function to add a new organization + * @param _pOrgId parent org id + * @param _orgId org id + * @param _level level in org hierarchy + * @param _status status of the org + */ + function _addNewOrg(string memory _pOrgId, string memory _orgId, + uint256 _level, uint _status) internal { + bytes32 pid = ""; + bytes32 oid = ""; + uint256 parentIndex = 0; + + if (_level == 1) {//root + oid = keccak256(abi.encodePacked(_orgId)); + } else { + pid = keccak256(abi.encodePacked(_pOrgId)); + oid = keccak256(abi.encodePacked(_pOrgId, ".", _orgId)); + } + orgNum++; + OrgIndex[oid] = orgNum; + uint256 id = orgList.length++; + if (_level == 1) { + orgList[id].level = _level; + orgList[id].pindex = 0; + orgList[id].fullOrgId = _orgId; + orgList[id].ultParent = _orgId; + } else { + parentIndex = OrgIndex[pid] - 1; + + require(orgList[parentIndex].subOrgIndexList.length < BREADTH_LIMIT, + "breadth level exceeded"); + require(orgList[parentIndex].level < DEPTH_LIMIT, + "depth level exceeded"); + + orgList[id].level = orgList[parentIndex].level + 1; + orgList[id].pindex = parentIndex; + orgList[id].ultParent = orgList[parentIndex].ultParent; + uint256 subOrgId = orgList[parentIndex].subOrgIndexList.length++; + orgList[parentIndex].subOrgIndexList[subOrgId] = id; + orgList[id].fullOrgId = string(abi.encodePacked(_pOrgId, ".", _orgId)); + } + orgList[id].orgId = _orgId; + orgList[id].parentId = _pOrgId; + orgList[id].status = _status; + if (_status == 1) { + emit OrgPendingApproval(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 1); + } + else { + emit OrgApproved(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 2); + } + } + + /** @notice returns the org index from the org list for the given org + * @return org index + */ + function _getOrgIndex(string memory _orgId) private view returns (uint){ + return OrgIndex[keccak256(abi.encodePacked(_orgId))] - 1; + } + +} diff --git a/permission/v1/contract/PermissionsImplementation.sol b/permission/v1/contract/PermissionsImplementation.sol new file mode 100644 index 0000000000..4c51999fc1 --- /dev/null +++ b/permission/v1/contract/PermissionsImplementation.sol @@ -0,0 +1,662 @@ +pragma solidity ^0.5.3; + +import "./RoleManager.sol"; +import "./AccountManager.sol"; +import "./VoterManager.sol"; +import "./NodeManager.sol"; +import "./OrgManager.sol"; +import "./PermissionsUpgradable.sol"; + +/** @title Permissions Implementation Contract + * @notice This contract holds implementation logic for all permissions + related functionality. This can be called only by the interface + contract. + */ +contract PermissionsImplementation { + AccountManager private accountManager; + RoleManager private roleManager; + VoterManager private voterManager; + NodeManager private nodeManager; + OrgManager private orgManager; + PermissionsUpgradable private permUpgradable; + + string private adminOrg; + string private adminRole; + string private orgAdminRole; + + + uint256 private fullAccess = 3; + + /** @dev this variable is meant for tracking the initial network boot up + once the network boot up is done the value is set to true + */ + bool private networkBoot = false; + + event PermissionsInitialized(bool _networkBootStatus); + + + /** @notice modifier to confirm that caller is the interface contract + */ + modifier onlyInterface{ + require(msg.sender == permUpgradable.getPermInterface(), + "can be called by interface contract only"); + _; + } + /** @notice modifier to confirm that caller is the upgradable contract + */ + modifier onlyUpgradeable { + require(msg.sender == address(permUpgradable), "invalid caller"); + _; + } + + /** @notice confirms if the network boot status is equal to passed value + * @param _status true/false + */ + modifier networkBootStatus(bool _status){ + require(networkBoot == _status, "Incorrect network boot status"); + _; + } + + /** @notice confirms that the account passed is network admin account + * @param _account account id + */ + modifier networkAdmin(address _account) { + require(isNetworkAdmin(_account) == true, "account is not a network admin account"); + _; + } + + /** @notice confirms that the account passed is org admin account + * @param _account account id + * @param _orgId org id to which the account belongs + */ + modifier orgAdmin(address _account, string memory _orgId) { + require(isOrgAdmin(_account, _orgId) == true, "account is not a org admin account"); + _; + } + + /** @notice confirms that org does not exist + * @param _orgId org id + */ + modifier orgNotExists(string memory _orgId) { + require(_checkOrgExists(_orgId) != true, "org exists"); + _; + } + + /** @notice confirms that org exists + * @param _orgId org id + */ + modifier orgExists(string memory _orgId) { + require(_checkOrgExists(_orgId) == true, "org does not exist"); + _; + } + + /** @notice checks of the passed org id is in approved status + * @param _orgId org id + */ + modifier orgApproved(string memory _orgId) { + require(checkOrgApproved(_orgId) == true, "org not in approved status"); + _; + } + + /** @notice constructor accepts the contracts addresses of other deployed + contracts of the permissions model + * @param _permUpgradable - address of permissions upgradable contract + * @param _orgManager - address of org manager contract + * @param _rolesManager - address of role manager contract + * @param _accountManager - address of account manager contract + * @param _voterManager - address of voter manager contract + * @param _nodeManager - address of node manager contract + */ + constructor (address _permUpgradable, address _orgManager, address _rolesManager, + address _accountManager, address _voterManager, address _nodeManager) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + orgManager = OrgManager(_orgManager); + roleManager = RoleManager(_rolesManager); + accountManager = AccountManager(_accountManager); + voterManager = VoterManager(_voterManager); + nodeManager = NodeManager(_nodeManager); + } + + // initial set up related functions + /** @notice for permissions its necessary to define the initial admin org + id, network admin role id and default org admin role id. this + sets these values at the time of network boot up + * @param _nwAdminOrg - address of permissions upgradable contract + * @param _nwAdminRole - address of org manager contract + * @param _oAdminRole - address of role manager contract + * @dev this function will be executed only once as part of the boot up + */ + function setPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, + string calldata _oAdminRole) external onlyInterface + networkBootStatus(false) { + adminOrg = _nwAdminOrg; + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + } + + /** @notice when migrating implementation contract, the values of these + key values need to be set from the previous implementation + contract. this function allows these values to be set + * @param _nwAdminOrg - address of permissions upgradable contract + * @param _nwAdminRole - address of org manager contract + * @param _oAdminRole - address of role manager contract + * @param _networkBootStatus - network boot status true/false + */ + function setMigrationPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, + string calldata _oAdminRole, bool _networkBootStatus) external onlyUpgradeable + networkBootStatus(false) { + adminOrg = _nwAdminOrg; + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + networkBoot = _networkBootStatus; + } + + /** @notice called at the time of network initialization. sets up + network admin org with allowed sub org depth and breadth + creates the network admin for the network admin org + sets the default values required by account manager contract + * @param _breadth - number of sub orgs allowed at parent level + * @param _depth - levels of sub org nesting allowed at parent level + */ + function init(uint256 _breadth, uint256 _depth) external + onlyInterface + networkBootStatus(false) { + orgManager.setUpOrg(adminOrg, _breadth, _depth); + roleManager.addRole(adminRole, adminOrg, fullAccess, true, true); + accountManager.setDefaults(adminRole, orgAdminRole); + } + /** @notice as a part of network initialization add all nodes which + are part of static-nodes.json as nodes belonging to + network admin org + * @param _enodeId - full enode id + */ + function addAdminNode(string calldata _enodeId) external + onlyInterface + networkBootStatus(false) { + nodeManager.addAdminNode(_enodeId, adminOrg); + } + + /** @notice as a part of network initialization add all accounts which are + passed via permission-config.json as network administrator + accounts + * @param _account - account id + */ + function addAdminAccount(address _account) external + onlyInterface + networkBootStatus(false) { + updateVoterList(adminOrg, _account, true); + accountManager.assignAdminRole(_account, adminOrg, adminRole, 2); + } + + /** @notice once the network initialization is complete, sets the network + boot status to true + * @return network boot status + * @dev this will be called only once from geth as a part of + * @dev network initialization + */ + function updateNetworkBootStatus() external + onlyInterface + networkBootStatus(false) + returns (bool){ + networkBoot = true; + emit PermissionsInitialized(networkBoot); + return networkBoot; + } + + /** @notice function to add a new organization to the network. creates org + record and marks it as pending approval. adds the passed node + node manager contract. adds the account with org admin role to + account manager contracts. creates voting record for approval + by other network admin accounts + * @param _orgId unique organization id + * @param _enodeId full enode id linked to the organization + * @param _account account id. this will have the org admin privileges + */ + function addOrg(string calldata _orgId, string calldata _enodeId, + address _account, address _caller) external + onlyInterface + networkBootStatus(true) + networkAdmin(_caller) { + voterManager.addVotingItem(adminOrg, _orgId, _enodeId, _account, 1); + orgManager.addOrg(_orgId); + nodeManager.addNode(_enodeId, _orgId); + require(validateAccount(_account, _orgId) == true, + "Operation cannot be performed"); + accountManager.assignAdminRole(_account, _orgId, orgAdminRole, 1); + } + + /** @notice functions to approve a pending approval org record by networ + admin account. once majority votes are received the org is + marked as approved + * @param _orgId unique organization id + * @param _enodeId full enode id linked to the organization + * @param _account account id this will have the org admin privileges + */ + function approveOrg(string calldata _orgId, string calldata _enodeId, + address _account, address _caller) external onlyInterface networkAdmin(_caller) { + require(_checkOrgStatus(_orgId, 1) == true, "Nothing to approve"); + if ((processVote(adminOrg, _caller, 1))) { + orgManager.approveOrg(_orgId); + roleManager.addRole(orgAdminRole, _orgId, fullAccess, true, true); + nodeManager.approveNode(_enodeId, _orgId); + accountManager.addNewAdmin(_orgId, _account); + } + } + + /** @notice function to create a sub org under a given parent org. + * @param _pOrgId parent org id under which the sub org is being added + * @param _orgId unique id for the sub organization + * @param _enodeId full enode id linked to the sjb organization + * @dev _enodeId is optional. parent org id should contain the complete + org hierarchy from master org id to the immediate parent. The org + hierarchy is separated by. For example, if master org ABC has a + sub organization SUB1, then while creating the sub organization at + SUB1 level, the parent org should be given as ABC.SUB1 + */ + function addSubOrg(string calldata _pOrgId, string calldata _orgId, + string calldata _enodeId, address _caller) external onlyInterface + orgExists(_pOrgId) orgAdmin(_caller, _pOrgId) { + orgManager.addSubOrg(_pOrgId, _orgId); + string memory pOrgId = string(abi.encodePacked(_pOrgId, ".", _orgId)); + if (bytes(_enodeId).length > 0) { + nodeManager.addOrgNode(_enodeId, pOrgId); + } + } + + /** @notice function to update the org status. it updates the org status + and adds a voting item for network admins to approve + * @param _orgId unique id of the organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function updateOrgStatus(string calldata _orgId, uint256 _action, address _caller) + external onlyInterface networkAdmin(_caller) { + uint256 pendingOp; + pendingOp = orgManager.updateOrg(_orgId, _action); + voterManager.addVotingItem(adminOrg, _orgId, "", address(0), pendingOp); + } + + /** @notice function to approve org status change. the org status is + changed once the majority votes are received from network + admin accounts. + * @param _orgId unique id for the sub organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function approveOrgStatus(string calldata _orgId, uint256 _action, address _caller) + external onlyInterface networkAdmin(_caller) { + require((_action == 1 || _action == 2), "Operation not allowed"); + uint256 pendingOp; + uint256 orgStatus; + if (_action == 1) { + pendingOp = 2; + orgStatus = 3; + } + else if (_action == 2) { + pendingOp = 3; + orgStatus = 5; + } + require(_checkOrgStatus(_orgId, orgStatus) == true, "operation not allowed"); + if ((processVote(adminOrg, _caller, pendingOp))) { + orgManager.approveOrgStatusUpdate(_orgId, _action); + } + } + + // Role related functions + + /** @notice function to add new role definition to an organization + can be executed by the org admin account only + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + * @param _access account access type allowed for the role + * @param _voter bool indicates if the role is voter role or not + * @param _admin bool indicates if the role is an admin role + * @dev account access type can have of the following four values: + 0 - Read only + 1 - Transact access + 2 - Contract deployment access. Can transact as well + 3 - Full access + */ + function addNewRole(string calldata _roleId, string calldata _orgId, + uint256 _access, bool _voter, bool _admin, address _caller) external + onlyInterface orgApproved(_orgId) orgAdmin(_caller, _orgId) { + //add new roles can be created by org admins only + roleManager.addRole(_roleId, _orgId, _access, _voter, _admin); + } + + /** @notice function to remove a role definition from an organization + can be executed by the org admin account only + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + */ + function removeRole(string calldata _roleId, string calldata _orgId, + address _caller) external onlyInterface orgApproved(_orgId) + orgAdmin(_caller, _orgId) { + require(((keccak256(abi.encode(_roleId)) != keccak256(abi.encode(adminRole))) && + (keccak256(abi.encode(_roleId)) != keccak256(abi.encode(orgAdminRole)))), + "admin roles cannot be removed"); + roleManager.removeRole(_roleId, _orgId); + } + + // Account related functions + /** @notice function to assign network admin/org admin role to an account + this can be executed by network admin accounts only. it assigns + the role to the accounts and creates voting record for network + admin accounts + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _roleId role id to be assigned to the account + */ + function assignAdminRole(string calldata _orgId, address _account, + string calldata _roleId, address _caller) external + onlyInterface orgExists(_orgId) networkAdmin(_caller) { + accountManager.assignAdminRole(_account, _orgId, _roleId, 1); + //add voting item + voterManager.addVotingItem(adminOrg, _orgId, "", _account, 4); + } + + /** @notice function to approve network admin/org admin role assigment + this can be executed by network admin accounts only. + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + */ + function approveAdminRole(string calldata _orgId, address _account, + address _caller) external onlyInterface networkAdmin(_caller) { + if ((processVote(adminOrg, _caller, 4))) { + (bool ret, address account) = accountManager.removeExistingAdmin(_orgId); + if (ret) { + updateVoterList(adminOrg, account, false); + } + bool ret1 = accountManager.addNewAdmin(_orgId, _account); + if (ret1) { + updateVoterList(adminOrg, _account, true); + } + } + } + + /** @notice function to update account status. can be executed by org admin + account only. + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _action 1-suspend 2-activate back 3-blacklist + */ + function updateAccountStatus(string calldata _orgId, address _account, + uint256 _action, address _caller) external onlyInterface + orgAdmin(_caller, _orgId) { + // ensure that the action passed to this call is proper and is not + // called with action 4 and 5 which are actions for blacklisted account + // recovery + require((_action == 1 || _action == 2 || _action == 3), + "invalid action. operation not allowed"); + accountManager.updateAccountStatus(_orgId, _account, _action); + } + + // Node related functions + + /** @notice function to add a new node to the organization. can be invoked + org admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being dded to the org + */ + function addNode(string calldata _orgId, string calldata _enodeId, address _caller) + external onlyInterface orgApproved(_orgId) orgAdmin(_caller, _orgId) { + // check that the node is not part of another org + nodeManager.addOrgNode(_enodeId, _orgId); + } + + /** @notice function to update node status. can be invoked by org admin + account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being dded to the org + * @param _action 1-deactivate, 2-activate back, 3-blacklist the node + */ + function updateNodeStatus(string calldata _orgId, string calldata _enodeId, + uint256 _action, address _caller) external onlyInterface + orgAdmin(_caller, _orgId) { + // ensure that the action passed to this call is proper and is not + // called with action 4 and 5 which are actions for blacklisted node + // recovery + require((_action == 1 || _action == 2 || _action == 3), + "invalid action. operation not allowed"); + nodeManager.updateNodeStatus(_enodeId, _orgId, _action); + } + + /** @notice function to initiate blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being dded to the org + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function startBlacklistedNodeRecovery(string calldata _orgId, string calldata _enodeId, + address _caller) external onlyInterface networkAdmin(_caller) { + // update the node status as recovery initiated. action for this is 4 + nodeManager.updateNodeStatus(_enodeId, _orgId, 4); + + // add a voting record with pending op of 5 which corresponds to blacklisted node + // recovery + voterManager.addVotingItem(adminOrg, _orgId, _enodeId, address(0), 5); + } + + /** @notice function to initiate blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being dded to the org + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function approveBlacklistedNodeRecovery(string calldata _orgId, string calldata _enodeId, + address _caller) external onlyInterface networkAdmin(_caller) { + // check if majority votes are received. pending op type is passed as 5 + // which stands for black listed node recovery + if ((processVote(adminOrg, _caller, 5))) { + // update the node back to active + nodeManager.updateNodeStatus(_enodeId, _orgId, 5); + } + } + + /** @notice function to initaite blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being dded to the org + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function startBlacklistedAccountRecovery(string calldata _orgId, address _account, + address _caller) external onlyInterface networkAdmin(_caller) { + // update the account status as recovery initiated. action for this is 4 + accountManager.updateAccountStatus(_orgId, _account, 4); + // add a voting record with pending op of 5 which corresponds to blacklisted node + // recovery + voterManager.addVotingItem(adminOrg, _orgId, "", _account, 6); + } + + /** @notice function to initaite blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being dded to the org + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function approveBlacklistedAccountRecovery(string calldata _orgId, address _account, + address _caller) external onlyInterface networkAdmin(_caller) { + // check if majority votes are received. pending op type is passed as 6 + // which stands for black listed account recovery + if ((processVote(adminOrg, _caller, 6))) { + // update the node back to active + accountManager.updateAccountStatus(_orgId, _account, 5); + } + } + + /** @notice function to fetch network boot status + * @return bool network boot status + */ + function getNetworkBootStatus() external view + returns (bool){ + return networkBoot; + } + + /** @notice function to fetch detail of any pending approval activities + for network admin organization + * @param _orgId unique id of the organization to which the account belongs + */ + function getPendingOp(string calldata _orgId) external view + returns (string memory, string memory, address, uint256){ + return voterManager.getPendingOpDetails(_orgId); + } + + /** @notice function to assigns a role id to the account given account + can be executed by org admin account only + * @param _account account id + * @param _orgId organization id to which the account belongs + * @param _roleId role id to be assigned to the account + */ + function assignAccountRole(address _account, string memory _orgId, + string memory _roleId, address _caller) public + onlyInterface + orgAdmin(_caller, _orgId) + orgApproved(_orgId) { + require(validateAccount(_account, _orgId) == true, "operation cannot be performed"); + require(_roleExists(_roleId, _orgId) == true, "role does not exists"); + bool admin = roleManager.isAdminRole(_roleId, _orgId, _getUltimateParent(_orgId)); + accountManager.assignAccountRole(_account, _orgId, _roleId, admin); + } + + /** @notice function to check if passed account is an network admin account + * @param _account account id + * @return true/false + */ + function isNetworkAdmin(address _account) public view + returns (bool){ + return (keccak256(abi.encode(accountManager.getAccountRole(_account))) == keccak256(abi.encode(adminRole))); + } + + /** @notice function to check if passed account is an org admin account + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function isOrgAdmin(address _account, string memory _orgId) public view + returns (bool){ + if (accountManager.checkOrgAdmin(_account, _orgId, _getUltimateParent(_orgId))) { + return true; + } + return roleManager.isAdminRole(accountManager.getAccountRole(_account), _orgId, + _getUltimateParent(_orgId)); + } + + /** @notice function to validate the account for access change operation + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function validateAccount(address _account, string memory _orgId) public view + returns (bool){ + return (accountManager.validateAccount(_account, _orgId)); + } + + /** @notice function to update the voter list at network level. this will + be called whenever an account is assigned a network admin role + or an account having network admin role is being assigned + different role + * @param _orgId org id to which the account belongs + * @param _account account which needs to be added/removed as voter + * @param _add bool indicating if its an add or delete operation + */ + function updateVoterList(string memory _orgId, address _account, bool _add) internal { + if (_add) { + voterManager.addVoter(_orgId, _account); + } + else { + voterManager.deleteVoter(_orgId, _account); + } + } + + /** @notice whenever a network admin account votes on a pending item, this + function processes the vote. + * @param _orgId org id of the caller + * @param _caller account which approving the operation + * @param _pendingOp operation for which the approval is being done + * @dev the list of pending ops are managed in voter manager contract + */ + function processVote(string memory _orgId, address _caller, uint256 _pendingOp) internal + returns (bool){ + return voterManager.processVote(_orgId, _caller, _pendingOp); + } + + /** @notice returns various permissions policy related parameters + * @return adminOrg admin org id + * @return adminRole default network admin role + * @return orgAdminRole default org admin role + * @return networkBoot network boot status + */ + function getPolicyDetails() external view + returns (string memory, string memory, string memory, bool){ + return (adminOrg, adminRole, orgAdminRole, networkBoot); + } + + /** @notice checks if the passed org exists or not + * @param _orgId org id + * @return true/false + */ + function _checkOrgExists(string memory _orgId) internal view + returns (bool){ + return orgManager.checkOrgExists(_orgId); + } + + /** @notice checks if the passed org is in approved status + * @param _orgId org id + * @return true/false + */ + function checkOrgApproved(string memory _orgId) internal view + returns (bool){ + return orgManager.checkOrgStatus(_orgId, 2); + } + + /** @notice checks if the passed org is in the status passed + * @param _orgId org id + * @param _status status to be checked for + * @return true/false + */ + function _checkOrgStatus(string memory _orgId, uint256 _status) internal view + returns (bool){ + return orgManager.checkOrgStatus(_orgId, _status); + } + + /** @notice checks if org admin account exists for the passed org id + * @param _orgId org id + * @return true/false + */ + function _checkOrgAdminExists(string memory _orgId) internal view + returns (bool){ + return accountManager.orgAdminExists(_orgId); + } + + /** @notice checks if role id exists for the passed org_id + * @param _roleId role id + * @param _orgId org id + * @return true/false + */ + function _roleExists(string memory _roleId, string memory _orgId) internal view + returns (bool){ + return roleManager.roleExists(_roleId, _orgId, _getUltimateParent(_orgId)); + } + + /** @notice checks if the role id for the org is a voter role + * @param _roleId role id + * @param _orgId org id + * @return true/false + */ + function _isVoterRole(string memory _roleId, string memory _orgId) internal view + returns (bool){ + return roleManager.isVoterRole(_roleId, _orgId, _getUltimateParent(_orgId)); + } + + /** @notice returns the ultimate parent for a given org id + * @param _orgId org id + * @return ultimate parent org id + */ + function _getUltimateParent(string memory _orgId) internal view + returns (string memory){ + return orgManager.getUltimateParent(_orgId); + } + +} \ No newline at end of file diff --git a/permission/v1/contract/PermissionsInterface.sol b/permission/v1/contract/PermissionsInterface.sol new file mode 100644 index 0000000000..d903adf401 --- /dev/null +++ b/permission/v1/contract/PermissionsInterface.sol @@ -0,0 +1,302 @@ +pragma solidity ^0.5.3; + +import "./PermissionsImplementation.sol"; +import "./PermissionsUpgradable.sol"; + +/** @title Permissions Interface Contract + * @notice This contract is the interface for permissions implementation + contract. for any call, it forwards the call to the implementation + contract + */ +contract PermissionsInterface { + PermissionsImplementation private permImplementation; + PermissionsUpgradable private permUpgradable; + address private permImplUpgradeable; + + /** @notice constructor + * @param _permImplUpgradeable permissions upgradable contract address + */ + constructor(address _permImplUpgradeable) public { + permImplUpgradeable = _permImplUpgradeable; + } + + /** @notice confirms that the caller is the address of upgradable + contract + */ + modifier onlyUpgradeable { + require(msg.sender == permImplUpgradeable, "invalid caller"); + _; + } + + /** @notice interface for setting the permissions policy in implementation + * @param _nwAdminOrg network admin organization id + * @param _nwAdminRole default network admin role id + * @param _oAdminRole default organization admin role id + */ + function setPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, + string calldata _oAdminRole) external { + permImplementation.setPolicy(_nwAdminOrg, _nwAdminRole, _oAdminRole); + } + + /** @notice interface to initializes the breadth and depth values for + sub organization management + * @param _breadth controls the number of sub org a parent org can have + * @param _depth controls the depth of nesting allowed for sub orgs + */ + function init(uint256 _breadth, uint256 _depth) external { + permImplementation.init(_breadth, _depth); + } + + /** @notice interface to add new node to an admin organization + * @param _enodeId full enode id of the node to be added + */ + function addAdminNode(string calldata _enodeId) external { + permImplementation.addAdminNode(_enodeId); + } + + /** @notice interface to add accounts to an admin organization + * @param _acct account address to be added + */ + function addAdminAccount(address _acct) external { + permImplementation.addAdminAccount(_acct); + } + + /** @notice interface to update network boot up status + * @return bool true or false + */ + function updateNetworkBootStatus() external + returns (bool) + { + return permImplementation.updateNetworkBootStatus(); + } + + /** @notice interface to fetch network boot status + * @return bool network boot status + */ + function getNetworkBootStatus() external view returns (bool){ + return permImplementation.getNetworkBootStatus(); + } + + /** @notice interface to add a new organization to the network + * @param _orgId unique organization id + * @param _enodeId full enode id linked to the organization + * @param _account account id. this will have the org admin privileges + */ + function addOrg(string calldata _orgId, string calldata _enodeId, + address _account) external { + permImplementation.addOrg(_orgId, _enodeId, _account, msg.sender); + } + + /** @notice interface to approve a newly added organization + * @param _orgId unique organization id + * @param _enodeId full enode id linked to the organization + * @param _account account id this will have the org admin privileges + */ + function approveOrg(string calldata _orgId, string calldata _enodeId, + address _account) external { + permImplementation.approveOrg(_orgId, _enodeId, _account, msg.sender); + } + + /** @notice interface to add sub org under an org + * @param _pOrgId parent org id under which the sub org is being added + * @param _orgId unique id for the sub organization + * @param _enodeId full enode id linked to the sjb organization + */ + function addSubOrg(string calldata _pOrgId, string calldata _orgId, + string calldata _enodeId) external { + permImplementation.addSubOrg(_pOrgId, _orgId, _enodeId, msg.sender); + } + + /** @notice interface to update the org status + * @param _orgId unique id of the organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function updateOrgStatus(string calldata _orgId, uint256 _action) external { + permImplementation.updateOrgStatus(_orgId, _action, msg.sender); + } + + /** @notice interface to approve org status change + * @param _orgId unique id for the sub organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function approveOrgStatus(string calldata _orgId, uint256 _action) external { + permImplementation.approveOrgStatus(_orgId, _action, msg.sender); + } + + /** @notice interface to add a new role definition to an organization + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + * @param _access account access type for the role + * @param _voter bool indicates if the role is voter role or not + * @param _admin bool indicates if the role is an admin role + * @dev account access type can have of the following four values: + 0 - Read only + 1 - Transact access + 2 - Contract deployment access. Can transact as well + 3 - Full access + */ + function addNewRole(string calldata _roleId, string calldata _orgId, + uint256 _access, bool _voter, bool _admin) external { + permImplementation.addNewRole(_roleId, _orgId, _access, _voter, _admin, msg.sender); + } + + /** @notice interface to remove a role definition from an organization + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + */ + function removeRole(string calldata _roleId, string calldata _orgId) external { + permImplementation.removeRole(_roleId, _orgId, msg.sender); + } + + /** @notice interface to assign network admin/org admin role to an account + this can be executed by network admin accounts only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _roleId role id to be assigned to the account + */ + function assignAdminRole(string calldata _orgId, address _account, + string calldata _roleId) external { + permImplementation.assignAdminRole(_orgId, _account, _roleId, msg.sender); + + } + /** @notice interface to approve network admin/org admin role assigment + this can be executed by network admin accounts only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + */ + function approveAdminRole(string calldata _orgId, address _account) external { + permImplementation.approveAdminRole(_orgId, _account, msg.sender); + + } + + /** @notice interface to update account status + this can be executed by org admin accounts only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _action 1-suspending 2-activating back 3-blacklisting + */ + function updateAccountStatus(string calldata _orgId, address _account, + uint256 _action) external { + permImplementation.updateAccountStatus(_orgId, _account, _action, msg.sender); + } + + /** @notice interface to add a new node to the organization + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being dded to the org + */ + function addNode(string calldata _orgId, string calldata _enodeId) external { + permImplementation.addNode(_orgId, _enodeId, msg.sender); + + } + + /** @notice interface to update node status + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being dded to the org + * @param _action 1-deactivate, 2-activate back, 3-blacklist the node + */ + function updateNodeStatus(string calldata _orgId, string calldata _enodeId, + uint256 _action) external { + permImplementation.updateNodeStatus(_orgId, _enodeId, _action, msg.sender); + } + + /** @notice interface to initiate blacklisted node recovery + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being recovered + */ + function startBlacklistedNodeRecovery(string calldata _orgId, string calldata _enodeId) + external { + permImplementation.startBlacklistedNodeRecovery(_orgId, _enodeId, msg.sender); + } + + /** @notice interface to approve blacklisted node recoevry + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId full enode id being recovered + */ + function approveBlacklistedNodeRecovery(string calldata _orgId, string calldata _enodeId) + external { + permImplementation.approveBlacklistedNodeRecovery(_orgId, _enodeId, msg.sender); + } + + /** @notice interface to initiate blacklisted account recovery + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being recovered + */ + function startBlacklistedAccountRecovery(string calldata _orgId, address _account) + external { + permImplementation.startBlacklistedAccountRecovery(_orgId, _account, msg.sender); + } + + /** @notice interface to approve blacklisted node recovery + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being recovered + */ + function approveBlacklistedAccountRecovery(string calldata _orgId, address _account) + external { + permImplementation.approveBlacklistedAccountRecovery(_orgId, _account, msg.sender); + } + + /** @notice interface to fetch detail of any pending approval activities + for network admin organization + * @param _orgId unique id of the organization to which the account belongs + */ + function getPendingOp(string calldata _orgId) external view + returns (string memory, string memory, address, uint256) { + return permImplementation.getPendingOp(_orgId); + } + + /** @notice sets the permissions implementation contract address + can be called from upgradable contract only + * @param _permImplementation permissions implementation contract address + */ + function setPermImplementation(address _permImplementation) external + onlyUpgradeable { + permImplementation = PermissionsImplementation(_permImplementation); + } + + /** @notice returns the address of permissions implementation contract + * @return permissions implementation contract address + */ + function getPermissionsImpl() external view returns (address) { + return address(permImplementation); + } + + /** @notice interface to assigns a role id to the account give + * @param _account account id + * @param _orgId organization id to which the account belongs + * @param _roleId role id to be assigned to the account + */ + function assignAccountRole(address _account, string calldata _orgId, + string calldata _roleId) external { + permImplementation.assignAccountRole(_account, _orgId, _roleId, msg.sender); + + } + + /** @notice interface to check if passed account is an network admin account + * @param _account account id + * @return true/false + */ + function isNetworkAdmin(address _account) external view returns (bool) { + return permImplementation.isNetworkAdmin(_account); + } + + /** @notice interface to check if passed account is an org admin account + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function isOrgAdmin(address _account, string calldata _orgId) + external view returns (bool) { + return permImplementation.isOrgAdmin(_account, _orgId); + } + + /** @notice interface to validate the account for access change operation + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function validateAccount(address _account, string calldata _orgId) + external view returns (bool) { + return permImplementation.validateAccount(_account, _orgId); + } + +} \ No newline at end of file diff --git a/permission/v1/contract/PermissionsUpgradable.sol b/permission/v1/contract/PermissionsUpgradable.sol new file mode 100644 index 0000000000..5adafcc7d5 --- /dev/null +++ b/permission/v1/contract/PermissionsUpgradable.sol @@ -0,0 +1,103 @@ +pragma solidity ^0.5.3; + +import "./PermissionsInterface.sol"; + +/** @title Permissions Upgradable Contract + * @notice This contract holds the address of current permissions implementation + contract. The contract is owned by a guardian account. Only the + guardian account can change the implementation contract address as + business needs. + */ +contract PermissionsUpgradable { + + address private guardian; + address private permImpl; + address private permInterface; + // initDone ensures that init can be called only once + bool private initDone; + + /** @notice constructor + * @param _guardian account address + */ + constructor (address _guardian) public{ + guardian = _guardian; + initDone = false; + } + + /** @notice confirms that the caller is the guardian account + */ + modifier onlyGuardian { + require(msg.sender == guardian, "invalid caller"); + _; + } + + /** @notice executed by guardian. Links interface and implementation contract + addresses. Can be executed by guardian account only + * @param _permInterface permissions interface contract address + * @param _permImpl implementation contract address + */ + function init(address _permInterface, address _permImpl) external + onlyGuardian { + require(!initDone, "can be executed only once"); + permImpl = _permImpl; + permInterface = _permInterface; + _setImpl(permImpl); + initDone = true; + } + + /** @notice changes the implementation contract address to the new address + address passed. Can be executed by guardian account only + * @param _proposedImpl address of the new permissions implementation contract + */ + function confirmImplChange(address _proposedImpl) public + onlyGuardian { + // The policy details needs to be carried forward from existing + // implementation to new. So first these are read from existing + // implementation and then updated in new implementation + (string memory adminOrg, string memory adminRole, string memory orgAdminRole, bool bootStatus) = PermissionsImplementation(permImpl).getPolicyDetails(); + _setPolicy(_proposedImpl, adminOrg, adminRole, orgAdminRole, bootStatus); + permImpl = _proposedImpl; + _setImpl(permImpl); + } + + /** @notice function to fetch the guardian account address + * @return _guardian guardian account address + */ + function getGuardian() public view returns (address) { + return guardian; + } + + /** @notice function to fetch the current implementation address + * @return permissions implementation contract address + */ + function getPermImpl() public view returns (address) { + return permImpl; + } + /** @notice function to fetch the interface address + * @return permissions interface contract address + */ + function getPermInterface() public view returns (address) { + return permInterface; + } + + /** @notice function to set the permissions policy details in the + permissions implementation contract + * @param _permImpl permissions implementation contract address + * @param _adminOrg name of admin organization + * @param _adminRole name of the admin role + * @param _orgAdminRole name of default organization admin role + * @param _bootStatus network boot status + */ + function _setPolicy(address _permImpl, string memory _adminOrg, string memory _adminRole, string memory _orgAdminRole, bool _bootStatus) private { + PermissionsImplementation(_permImpl).setMigrationPolicy(_adminOrg, _adminRole, _orgAdminRole, _bootStatus); + } + + /** @notice function to set the permissions implementation contract address + in the permissions interface contract + * @param _permImpl permissions implementation contract address + */ + function _setImpl(address _permImpl) private { + PermissionsInterface(permInterface).setPermImplementation(_permImpl); + } + +} \ No newline at end of file diff --git a/permission/v1/contract/RoleManager.sol b/permission/v1/contract/RoleManager.sol new file mode 100644 index 0000000000..c75b15d4a6 --- /dev/null +++ b/permission/v1/contract/RoleManager.sol @@ -0,0 +1,199 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; +/** @title Role manager contract + * @notice This contract holds implementation logic for all role management + functionality. This can be called only by the implementation + contract only. there are few view functions exposed as public and + can be called directly. these are invoked by quorum for populating + permissions data in cache + */ +contract RoleManager { + PermissionsUpgradable private permUpgradable; + + struct RoleDetails { + string roleId; + string orgId; + uint256 baseAccess; + bool isVoter; + bool isAdmin; + bool active; + } + + RoleDetails[] private roleList; + mapping(bytes32 => uint256) private roleIndex; + uint256 private numberOfRoles; + + event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, + bool _isVoter, bool _isAdmin); + event RoleRevoked(string _roleId, string _orgId); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice function to add a new role definition to an organization + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _baseAccess - 0-ReadOnly, 1-Transact, 2-ContractDeply, 3- Full + * @param _isVoter - bool to indicate if voter role or not + * @param _isAdmin - bool to indicate if admin role or not + * @dev base access can have any of the following values: + 0 - Read only + 1 - Transact only + 2 - Contract deploy. can transact as well + 3 - Full access + */ + function addRole(string memory _roleId, string memory _orgId, uint256 _baseAccess, + bool _isVoter, bool _isAdmin) public onlyImplementation { + // check if the role access passed is valid + require(_baseAccess < 4, "invalid access value"); + // Check if account already exists + require(roleIndex[keccak256(abi.encode(_roleId, _orgId))] == 0, "role exists for the org"); + numberOfRoles ++; + roleIndex[keccak256(abi.encode(_roleId, _orgId))] = numberOfRoles; + roleList.push(RoleDetails(_roleId, _orgId, _baseAccess, _isVoter, _isAdmin, true)); + emit RoleCreated(_roleId, _orgId, _baseAccess, _isVoter, _isAdmin); + + } + + /** @notice function to remove an existing role definition from an organization + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + */ + function removeRole(string calldata _roleId, string calldata _orgId) external + onlyImplementation { + require(roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0, "role does not exist"); + uint256 rIndex = _getRoleIndex(_roleId, _orgId); + roleList[rIndex].active = false; + emit RoleRevoked(_roleId, _orgId); + } + + /** @notice checks if the role is a voter role or not + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _ultParent - master org id + * @return true or false + * @dev checks for the role existence in the passed org and master org + */ + function isVoterRole(string calldata _roleId, string calldata _orgId, + string calldata _ultParent) external view onlyImplementation returns (bool){ + if (!(roleExists(_roleId, _orgId, _ultParent))) { + return false; + } + uint256 rIndex; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + rIndex = _getRoleIndex(_roleId, _orgId); + } + else { + rIndex = _getRoleIndex(_roleId, _ultParent); + } + return (roleList[rIndex].active && roleList[rIndex].isVoter); + } + + /** @notice checks if the role is an admin role or not + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _ultParent - master org id + * @return true or false + * @dev checks for the role existence in the passed org and master org + */ + function isAdminRole(string calldata _roleId, string calldata _orgId, + string calldata _ultParent) external view onlyImplementation returns (bool){ + if (!(roleExists(_roleId, _orgId, _ultParent))) { + return false; + } + uint256 rIndex; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + rIndex = _getRoleIndex(_roleId, _orgId); + } + else { + rIndex = _getRoleIndex(_roleId, _ultParent); + } + return (roleList[rIndex].active && roleList[rIndex].isAdmin); + } + + /** @notice returns the role details for a passed role id and org + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @return role id + * @return org id + * @return access type + * @return bool to indicate if the role is a voter role + * @return bool to indicate if the role is active + */ + function getRoleDetails(string calldata _roleId, string calldata _orgId) + external view returns (string memory roleId, string memory orgId, + uint256 accessType, bool voter, bool admin, bool active) { + if (!(roleExists(_roleId, _orgId, ""))) { + return (_roleId, "", 0, false, false, false); + } + uint256 rIndex = _getRoleIndex(_roleId, _orgId); + return (roleList[rIndex].roleId, roleList[rIndex].orgId, + roleList[rIndex].baseAccess, roleList[rIndex].isVoter, + roleList[rIndex].isAdmin, roleList[rIndex].active); + } + + /** @notice returns the role details for a passed role index + * @param _rIndex - unique identifier for the role being added + * @return role id + * @return org id + * @return access type + * @return bool to indicate if the role is a voter role + * @return bool to indicate if the role is active + */ + function getRoleDetailsFromIndex(uint256 _rIndex) external view returns + (string memory roleId, string memory orgId, uint256 accessType, + bool voter, bool admin, bool active) { + return (roleList[_rIndex].roleId, roleList[_rIndex].orgId, + roleList[_rIndex].baseAccess, roleList[_rIndex].isVoter, + roleList[_rIndex].isAdmin, roleList[_rIndex].active); + } + + /** @notice returns the total number of roles in the network + * @return total number of roles + */ + function getNumberOfRoles() external view returns (uint256) { + return roleList.length; + } + + /** @notice checks if the role exists for the given org or master org + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _ultParent - master org id + * @return true or false + */ + function roleExists(string memory _roleId, string memory _orgId, + string memory _ultParent) public view returns (bool) { + uint256 id; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + id = _getRoleIndex(_roleId, _orgId); + return roleList[id].active; + } + else if (roleIndex[keccak256(abi.encode(_roleId, _ultParent))] != 0) { + id = _getRoleIndex(_roleId, _ultParent); + return roleList[id].active; + } + return false; + } + + /** @notice returns the role index based on role id and org id + * @param _roleId - role id + * @param _orgId - org id + * @return role index + */ + function _getRoleIndex(string memory _roleId, string memory _orgId) + internal view returns (uint256) { + return roleIndex[keccak256(abi.encode(_roleId, _orgId))] - 1; + } +} diff --git a/permission/v1/contract/VoterManager.sol b/permission/v1/contract/VoterManager.sol new file mode 100644 index 0000000000..437a8f09c5 --- /dev/null +++ b/permission/v1/contract/VoterManager.sol @@ -0,0 +1,250 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; + +/** @title Voter manager contract + * @notice This contract holds implementation logic for all account voter and + voting functionality. This can be called only by the implementation + contract only. there are few view functions exposed as public and + can be called directly. these are invoked by quorum for populating + permissions data in cache + * @dev each voting record has an attribute operation type (opType) + which denotes the activity type which is pending approval. This can + have the following values: + 0 - None - indicates no pending records for the org + 1 - New org add activity + 2 - Org suspension activity + 3 - Revoke of org suspension + 4 - Assigning admin role for a new account + 5 - Blacklisted node recovery + 6 - Blacklisted account recovery + */ +contract VoterManager { + PermissionsUpgradable private permUpgradable; + struct PendingOpDetails { + string orgId; + string enodeId; + address account; + uint256 opType; + } + + struct Voter { + address vAccount; + bool active; + } + + struct OrgVoterDetails { + string orgId; + uint256 voterCount; + uint256 validVoterCount; + uint256 voteCount; + PendingOpDetails pendingOp; + Voter [] voterList; + mapping(address => uint256) voterIndex; + mapping(uint256 => mapping(address => bool)) votingStatus; + } + + OrgVoterDetails [] private orgVoterList; + mapping(bytes32 => uint256) private VoterOrgIndex; + uint256 private orgNum = 0; + + // events related to managing voting accounts for the org + event VoterAdded(string _orgId, address _vAccount); + event VoterDeleted(string _orgId, address _vAccount); + + event VotingItemAdded(string _orgId); + event VoteProcessed(string _orgId); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if account is a valid voter record and belongs to the org + passed + * @param _orgId - org id + * @param _vAccount - voter account passed + */ + modifier voterExists(string memory _orgId, address _vAccount) { + require(_checkVoterExists(_orgId, _vAccount) == true, "must be a voter"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice function to add a new voter account to the organization + * @param _orgId org id + * @param _vAccount - voter account + * @dev voter capability is currently enabled for network level activities + only. voting is not available for org related activities + */ + function addVoter(string calldata _orgId, address _vAccount) external + onlyImplementation { + // check if the org exists + if (VoterOrgIndex[keccak256(abi.encode(_orgId))] == 0) { + orgNum++; + VoterOrgIndex[keccak256(abi.encode(_orgId))] = orgNum; + uint256 id = orgVoterList.length++; + orgVoterList[id].orgId = _orgId; + orgVoterList[id].voterCount = 1; + orgVoterList[id].validVoterCount = 1; + orgVoterList[id].voteCount = 0; + orgVoterList[id].pendingOp.orgId = ""; + orgVoterList[id].pendingOp.enodeId = ""; + orgVoterList[id].pendingOp.account = address(0); + orgVoterList[id].pendingOp.opType = 0; + orgVoterList[id].voterIndex[_vAccount] = orgVoterList[id].voterCount; + orgVoterList[id].voterList.push(Voter(_vAccount, true)); + } + else { + uint256 id = _getVoterOrgIndex(_orgId); + // check if the voter is already present in the list + if (orgVoterList[id].voterIndex[_vAccount] == 0) { + orgVoterList[id].voterCount++; + orgVoterList[id].voterIndex[_vAccount] = orgVoterList[id].voterCount; + orgVoterList[id].voterList.push(Voter(_vAccount, true)); + orgVoterList[id].validVoterCount++; + } + else { + uint256 vid = _getVoterIndex(_orgId, _vAccount); + require(orgVoterList[id].voterList[vid].active != true, "already a voter"); + orgVoterList[id].voterList[vid].active = true; + orgVoterList[id].validVoterCount++; + } + + } + emit VoterAdded(_orgId, _vAccount); + } + + /** @notice function to delete a voter account from the organization + * @param _orgId org id + * @param _vAccount - voter account + * @dev voter capability is currently enabled for network level activities + only. voting is not available for org related activities + */ + function deleteVoter(string calldata _orgId, address _vAccount) external + onlyImplementation + voterExists(_orgId, _vAccount) { + uint256 id = _getVoterOrgIndex(_orgId); + uint256 vId = _getVoterIndex(_orgId, _vAccount); + orgVoterList[id].validVoterCount --; + orgVoterList[id].voterList[vId].active = false; + emit VoterDeleted(_orgId, _vAccount); + } + + /** @notice function to a voting item for network admin accounts to vote + * @param _authOrg org id of the authorizing org. it will be network admin org + * @param _orgId - org id for which the voting record is being created + * @param _enodeId - enode id for which the voting record is being created + * @param _account - account id for which the voting record is being created + * @param _pendingOp - operation for which voting is being done + */ + function addVotingItem(string calldata _authOrg, string calldata _orgId, + string calldata _enodeId, address _account, uint256 _pendingOp) + external onlyImplementation { + // check if anything is pending approval for the org. + // If yes another item cannot be added + require((_checkPendingOp(_authOrg, 0)), + "items pending for approval. new item cannot be added"); + uint256 id = _getVoterOrgIndex(_authOrg); + orgVoterList[id].pendingOp.orgId = _orgId; + orgVoterList[id].pendingOp.enodeId = _enodeId; + orgVoterList[id].pendingOp.account = _account; + orgVoterList[id].pendingOp.opType = _pendingOp; + // initialize vote status for voter accounts + for (uint256 i = 0; i < orgVoterList[id].voterList.length; i++) { + if (orgVoterList[id].voterList[i].active) { + orgVoterList[id].votingStatus[id][orgVoterList[id].voterList[i].vAccount] = false; + } + } + // set vote count to zero + orgVoterList[id].voteCount = 0; + emit VotingItemAdded(_authOrg); + + } + + /** @notice function processing vote of a voter account + * @param _authOrg org id of the authorizing org. it will be network admin org + * @param _vAccount - account id of the voter + * @param _pendingOp - operation which is being approved + * @return success of the voter process. either true or false + */ + function processVote(string calldata _authOrg, address _vAccount, uint256 _pendingOp) + external onlyImplementation voterExists(_authOrg, _vAccount) returns (bool) { + // check something if anything is pending approval + require(_checkPendingOp(_authOrg, _pendingOp) == true, "nothing to approve"); + uint256 id = _getVoterOrgIndex(_authOrg); + // check if vote is already processed + require(orgVoterList[id].votingStatus[id][_vAccount] != true, "cannot double vote"); + orgVoterList[id].voteCount++; + orgVoterList[id].votingStatus[id][_vAccount] = true; + emit VoteProcessed(_authOrg); + if (orgVoterList[id].voteCount > orgVoterList[id].validVoterCount / 2) { + // majority achieved, clean up pending op + orgVoterList[id].pendingOp.orgId = ""; + orgVoterList[id].pendingOp.enodeId = ""; + orgVoterList[id].pendingOp.account = address(0); + orgVoterList[id].pendingOp.opType = 0; + return true; + } + return false; + } + + /** @notice returns the details of any pending operation to be approved + * @param _orgId org id. this will be the org id of network admin org + */ + function getPendingOpDetails(string calldata _orgId) external view + onlyImplementation returns (string memory, string memory, address, uint256){ + uint256 orgIndex = _getVoterOrgIndex(_orgId); + return (orgVoterList[orgIndex].pendingOp.orgId, orgVoterList[orgIndex].pendingOp.enodeId, + orgVoterList[orgIndex].pendingOp.account, orgVoterList[orgIndex].pendingOp.opType); + } + + /** @notice checks if the voter account exists and is linked to the org + * @param _orgId org id + * @param _vAccount voter account id + * @return true or false + */ + function _checkVoterExists(string memory _orgId, address _vAccount) + internal view returns (bool){ + uint256 orgIndex = _getVoterOrgIndex(_orgId); + if (orgVoterList[orgIndex].voterIndex[_vAccount] == 0) { + return false; + } + uint256 voterIndex = _getVoterIndex(_orgId, _vAccount); + return orgVoterList[orgIndex].voterList[voterIndex].active; + } + + /** @notice checks if the pending operation exists or not + * @param _orgId org id + * @param _pendingOp type of operation + * @return true or false + */ + function _checkPendingOp(string memory _orgId, uint256 _pendingOp) + internal view returns (bool){ + return (orgVoterList[_getVoterOrgIndex(_orgId)].pendingOp.opType == _pendingOp); + } + + /** @notice returns the voter account index + */ + function _getVoterIndex(string memory _orgId, address _vAccount) + internal view returns (uint256) { + uint256 orgIndex = _getVoterOrgIndex(_orgId); + return orgVoterList[orgIndex].voterIndex[_vAccount] - 1; + } + + /** @notice returns the org index for the org from voter list + */ + function _getVoterOrgIndex(string memory _orgId) + internal view returns (uint256) { + return VoterOrgIndex[keccak256(abi.encode(_orgId))] - 1; + } + +} diff --git a/permission/v1/contract/gen/AccountManager.abi b/permission/v1/contract/gen/AccountManager.abi new file mode 100644 index 0000000000..7dfbdb8ee1 --- /dev/null +++ b/permission/v1/contract/gen/AccountManager.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"},{"name":"_adminRole","type":"bool"}],"name":"assignAccountRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"}],"name":"removeExistingAdmin","outputs":[{"name":"voterUpdate","type":"bool"},{"name":"account","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAccountDetails","outputs":[{"name":"","type":"address"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfAccounts","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"validateAccount","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAccountRole","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_action","type":"uint256"}],"name":"updateAccountStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"orgAdminExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_aIndex","type":"uint256"}],"name":"getAccountDetailsFromIndex","outputs":[{"name":"","type":"address"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"addNewAdmin","outputs":[{"name":"voterUpdate","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"}],"name":"setDefaults","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"},{"name":"_status","type":"uint256"}],"name":"assignAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"checkOrgAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_account","type":"address"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgAdmin","type":"bool"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"AccountAccessModified","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_account","type":"address"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgAdmin","type":"bool"}],"name":"AccountAccessRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_account","type":"address"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"AccountStatusChanged","type":"event"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/AccountManager.bin b/permission/v1/contract/gen/AccountManager.bin new file mode 100644 index 0000000000..19472c4b00 --- /dev/null +++ b/permission/v1/contract/gen/AccountManager.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50604051602080613a0c8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556139aa806100626000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806384b7a84a1161008c578063c214e5e511610066578063c214e5e5146105eb578063cef7f6af14610662578063e3483a9d14610720578063e8b42bf4146107ee576100cf565b806384b7a84a146104ad578063950145cf1461052a578063b2018568146105ce576100cf565b8063143a5604146100d45780631d09dc93146101a65780632aceb53414610237578063309e36ef146103665780636b568d761461038057806381d66b2314610412575b600080fd5b6101a4600480360360808110156100ea57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561011457600080fd5b82018360208201111561012657600080fd5b803590602001918460018302840111600160201b8311171561014757600080fd5b919390929091602081019035600160201b81111561016457600080fd5b82018360208201111561017657600080fd5b803590602001918460018302840111600160201b8311171561019757600080fd5b9193509150351515610927565b005b610214600480360360208110156101bc57600080fd5b810190602081018135600160201b8111156101d657600080fd5b8201836020820111156101e857600080fd5b803590602001918460018302840111600160201b8311171561020957600080fd5b509092509050610d25565b6040805192151583526001600160a01b0390911660208301528051918290030190f35b61025d6004803603602081101561024d57600080fd5b50356001600160a01b03166112b1565b60405180866001600160a01b03166001600160a01b03168152602001806020018060200185815260200184151515158152602001838103835287818151815260200191508051906020019080838360005b838110156102c65781810151838201526020016102ae565b50505050905090810190601f1680156102f35780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b8381101561032657818101518382015260200161030e565b50505050905090810190601f1680156103535780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b61036e611509565b60408051918252519081900360200190f35b6103fe6004803603604081101561039657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156103c057600080fd5b8201836020820111156103d257600080fd5b803590602001918460018302840111600160201b831117156103f357600080fd5b509092509050611510565b604080519115158252519081900360200190f35b6104386004803603602081101561042857600080fd5b50356001600160a01b031661166b565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561047257818101518382015260200161045a565b50505050905090810190601f16801561049f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101a4600480360360608110156104c357600080fd5b810190602081018135600160201b8111156104dd57600080fd5b8201836020820111156104ef57600080fd5b803590602001918460018302840111600160201b8311171561051057600080fd5b91935091506001600160a01b0381351690602001356117c1565b6103fe6004803603602081101561054057600080fd5b810190602081018135600160201b81111561055a57600080fd5b82018360208201111561056c57600080fd5b803590602001918460018302840111600160201b8311171561058d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611ef4945050505050565b61025d600480360360208110156105e457600080fd5b503561206f565b6103fe6004803603604081101561060157600080fd5b810190602081018135600160201b81111561061b57600080fd5b82018360208201111561062d57600080fd5b803590602001918460018302840111600160201b8311171561064e57600080fd5b9193509150356001600160a01b0316612259565b6101a46004803603604081101561067857600080fd5b810190602081018135600160201b81111561069257600080fd5b8201836020820111156106a457600080fd5b803590602001918460018302840111600160201b831117156106c557600080fd5b919390929091602081019035600160201b8111156106e257600080fd5b8201836020820111156106f457600080fd5b803590602001918460018302840111600160201b8311171561071557600080fd5b5090925090506128ab565b6101a46004803603608081101561073657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561076057600080fd5b82018360208201111561077257600080fd5b803590602001918460018302840111600160201b8311171561079357600080fd5b919390929091602081019035600160201b8111156107b057600080fd5b8201836020820111156107c257600080fd5b803590602001918460018302840111600160201b831117156107e357600080fd5b919350915035612997565b6103fe6004803603606081101561080457600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561082e57600080fd5b82018360208201111561084057600080fd5b803590602001918460018302840111600160201b8311171561086157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156108b357600080fd5b8201836020820111156108c557600080fd5b803590602001918460018302840111600160201b831117156108e657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612d09945050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561097457600080fd5b505afa158015610988573d6000803e3d6000fd5b505050506040513d602081101561099e57600080fd5b50516001600160a01b031633146109f35760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015610a765780601f10610a4b57610100808354040283529160200191610a76565b820191906000526020600020905b815481529060010190602001808311610a5957829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014158015610c6457506040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015610b715780601f10610b4657610100808354040283529160200191610b71565b820191906000526020600020905b815481529060010190602001808311610b5457829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040526040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610c19578181015183820152602001610c01565b50505050905090810190601f168015610c465780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012014155b1515610ca457604051600160e51b62461bcd0281526004018080602001828103825260408152602001806138da6040913960400191505060405180910390fd5b610d1d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250600292508791506132709050565b505050505050565b6000805460408051600160e41b62e32cf9028152905183926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610d6c57600080fd5b505afa158015610d80573d6000803e3d6000fd5b505050506040513d6020811015610d9657600080fd5b50516001600160a01b03163314610deb5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b610e2a84848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611ef492505050565b156112a3576000610eb260066000878760405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a90046001600160a01b031661364a565b90506006600182815481101515610ec557fe5b9060005260206000209060050201600301819055506000600182815481101515610eeb57fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776600182815481101515610f4357fe5b6000918252602090912060059091020154600180546001600160a01b039092169184908110610f6e57fe5b9060005260206000209060050201600101600184815481101515610f8e57fe5b9060005260206000209060050201600201600185815481101515610fae57fe5b60009182526020909120600460059092020101546001805460ff9092169187908110610fd657fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156110895780601f1061105e57610100808354040283529160200191611089565b820191906000526020600020905b81548152906001019060200180831161106c57829003601f168201915b50508381038252865460026000196101006001841615020190911604808252602090910190879080156110fd5780601f106110d2576101008083540402835291602001916110fd565b820191906000526020600020905b8154815290600101906020018083116110e057829003601f168201915b505097505050505050505060405180910390a160408051602080820190815260048054600260001961010060018416150201909116049383018490529290918291606090910190849080156111935780601f1061116857610100808354040283529160200191611193565b820191906000526020600020905b81548152906001019060200180831161117657829003601f168201915b505092505050604051602081830303815290604052805190602001206001828154811015156111be57fe5b6000918252602091829020604080518085019485526002600590940290920183018054600019610100600183161502011693909304908201819052919291829160600190849080156112515780601f1061122657610100808354040283529160200191611251565b820191906000526020600020905b81548152906001019060200180831161123457829003601f168201915b505092505050604051602081830303815290604052805190602001201460018281548110151561127d57fe5b60009182526020909120600590910201549093506001600160a01b031691506112aa9050565b5060009050805b9250929050565b6001600160a01b038116600090815260026020526040812054606090819083908190151561131857505060408051808201825260048152600160e01b634e4f4e45026020808301919091528251908101909252600080835286955090935090915080611500565b60006113238761364a565b905060018181548110151561133457fe5b6000918252602090912060059091020154600180546001600160a01b03909216918390811061135f57fe5b906000526020600020906005020160010160018381548110151561137f57fe5b906000526020600020906005020160020160018481548110151561139f57fe5b9060005260206000209060050201600301546001858154811015156113c057fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff9092169286919083018282801561145f5780601f106114345761010080835404028352916020019161145f565b820191906000526020600020905b81548152906001019060200180831161144257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156114ed5780601f106114c2576101008083540402835291602001916114ed565b820191906000526020600020905b8154815290600101906020018083116114d057829003601f168201915b5050505050925095509550955095509550505b91939590929450565b6001545b90565b6001600160a01b038316600090815260026020526040812054151561153757506001611664565b60006115428561364a565b9050838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001206001828154811015156115a657fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156116435780601f1061161857610100808354040283529160200191611643565b820191906000526020600020905b81548152906001019060200180831161162657829003601f168201915b50509250505060405160208183030381529060405280519060200120149150505b9392505050565b6001600160a01b03811660009081526002602052604090205460609015156116b157506040805180820190915260048152600160e01b634e4f4e450260208201526117bc565b60006116bc8361364a565b90506001818154811015156116cd57fe5b906000526020600020906005020160030154600014151561179b5760018054829081106116f657fe5b600091825260209182902060026005909202018101805460408051601f60001961010060018616150201909316949094049182018590048502840185019052808352919290919083018282801561178e5780601f106117635761010080835404028352916020019161178e565b820191906000526020600020905b81548152906001019060200180831161177157829003601f168201915b50505050509150506117bc565b50506040805180820190915260048152600160e01b634e4f4e450260208201525b919050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561180e57600080fd5b505afa158015611822573d6000803e3d6000fd5b505050506040513d602081101561183857600080fd5b50516001600160a01b0316331461188d5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506001600160a01b0387168152600260205260409020548693501515915061193290505760408051600160e51b62461bcd02815260206004820152601760248201527f6163636f756e7420646f6573206e6f7420657869737473000000000000000000604482015290519081900360640190fd5b816040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561197357818101518382015260200161195b565b50505050905090810190601f1680156119a05780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060016119c68361364a565b815481106119d057fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611a6d5780601f10611a4257610100808354040283529160200191611a6d565b820191906000526020600020905b815481529060010190602001808311611a5057829003601f168201915b50509250505060405160208183030381529060405280519060200120141515611ae05760408051600160e51b62461bcd02815260206004820152601860248201527f6163636f756e7420696e20646966666572656e74206f72670000000000000000604482015290519081900360640190fd5b600083118015611af05750600683105b1515611b465760408051600160e51b62461bcd02815260206004820152601d60248201527f696e76616c696420737461747573206368616e67652072657175657374000000604482015290519081900360640190fd5b611b948487878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602081019091529081529250612d09915050565b151560011415611bd857604051600160e51b62461bcd02815260040180806020018281038252603181526020018061394e6031913960400191505060405180910390fd5b60008360011415611c55576001611bee8661364a565b81548110611bf857fe5b9060005260206000209060050201600301546002141515611c4d57604051600160e51b62461bcd0281526004018080602001828103825260398152602001806138796039913960400191505060405180910390fd5b506004611e3e565b8360021415611cd0576001611c698661364a565b81548110611c7357fe5b9060005260206000209060050201600301546004141515611cc857604051600160e51b62461bcd02815260040180806020018281038252603c81526020018061383d603c913960400191505060405180910390fd5b506002611e3e565b8360031415611d4c576001611ce48661364a565b81548110611cee57fe5b906000526020600020906005020160030154600514151515611d4457604051600160e51b62461bcd0281526004018080602001828103825260388152602001806138056038913960400191505060405180910390fd5b506005611e3e565b8360041415611dc7576001611d608661364a565b81548110611d6a57fe5b9060005260206000209060050201600301546005141515611dbf57604051600160e51b62461bcd02815260040180806020018281038252603481526020018061391a6034913960400191505060405180910390fd5b506007611e3e565b8360051415611e3e576001611ddb8661364a565b81548110611de557fe5b9060005260206000209060050201600301546007141515611e3a57604051600160e51b62461bcd0281526004018080602001828103825260388152602001806137cd6038913960400191505060405180910390fd5b5060025b806001611e4a8761364a565b81548110611e5457fe5b9060005260206000209060050201600301819055507f36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b258588888460405180856001600160a01b03166001600160a01b03168152602001806020018381526020018281038252858582818152602001925080828437600083820152604051601f909101601f191690920182900397509095505050505050a150505050505050565b6000806001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611f45578181015183820152602001611f2d565b50505050905090810190601f168015611f725780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b03161461206757600060066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611ff1578181015183820152602001611fd9565b50505050905090810190601f16801561201e5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316905061205c81613669565b6002149150506117bc565b506000919050565b600060608060008060018681548110151561208657fe5b6000918252602090912060059091020154600180546001600160a01b0390921691889081106120b157fe5b90600052602060002090600502016001016001888154811015156120d157fe5b90600052602060002090600502016002016001898154811015156120f157fe5b90600052602060002090600502016003015460018a81548110151561211257fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff909216928691908301828280156121b15780601f10612186576101008083540402835291602001916121b1565b820191906000526020600020905b81548152906001019060200180831161219457829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529599508894509250840190508282801561223f5780601f106122145761010080835404028352916020019161223f565b820191906000526020600020905b81548152906001019060200180831161222257829003601f168201915b505050505092509450945094509450945091939590929450565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156122a857600080fd5b505afa1580156122bc573d6000803e3d6000fd5b505050506040513d60208110156122d257600080fd5b50516001600160a01b031633146123275760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60606123328361166b565b9050600061233f84613669565b9050600061234c8561364a565b604080516020808201908152600580546002600019610100600184161502019091160493830184905293945091829160600190849080156123ce5780601f106123a3576101008083540402835291602001916123ce565b820191906000526020600020905b8154815290600101906020018083116123b157829003601f168201915b50509250505060405160208183030381529060405280519060200120836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561242b578181015183820152602001612413565b50505050905090810190601f1680156124585780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001201480156124805750816001145b15612510578460066000898960405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b600260018281548110151561252157fe5b9060005260206000209060050201600301819055506001808281548110151561254657fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc7768560018381548110151561259f57fe5b90600052602060002090600502016001016001848154811015156125bf57fe5b90600052602060002090600502016002016001858154811015156125df57fe5b60009182526020909120600460059092020101546001805460ff909216918790811061260757fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156126ba5780601f1061268f576101008083540402835291602001916126ba565b820191906000526020600020905b81548152906001019060200180831161269d57829003601f168201915b505083810382528654600260001961010060018416150201909116048082526020909101908790801561272e5780601f106127035761010080835404028352916020019161272e565b820191906000526020600020905b81548152906001019060200180831161271157829003601f168201915b505097505050505050505060405180910390a160408051602080820190815260048054600260001961010060018416150201909116049383018490529290918291606090910190849080156127c45780601f10612799576101008083540402835291602001916127c4565b820191906000526020600020905b8154815290600101906020018083116127a757829003601f168201915b505092505050604051602081830303815290604052805190602001206001828154811015156127ef57fe5b6000918252602091829020604080518085019485526002600590940290920183018054600019610100600183161502011693909304908201819052919291829160600190849080156128825780601f1061285757610100808354040283529160200191612882565b820191906000526020600020905b81548152906001019060200180831161286557829003601f168201915b505092505050604051602081830303815290604052805190602001201493505050509392505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156128f857600080fd5b505afa15801561290c573d6000803e3d6000fd5b505050506040513d602081101561292257600080fd5b50516001600160a01b031633146129775760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b612983600485856136c6565b50612990600583836136c6565b5050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156129e457600080fd5b505afa1580156129f8573d6000803e3d6000fd5b505050506040513d6020811015612a0e57600080fd5b50516001600160a01b03163314612a635760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015612ae65780601f10612abb57610100808354040283529160200191612ae6565b820191906000526020600020905b815481529060010190602001808311612ac957829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001201480612c5057506040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015612bdf5780601f10612bb457610100808354040283529160200191612bdf565b820191906000526020600020905b815481529060010190602001808311612bc257829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120145b1515612c9057604051600160e51b62461bcd0281526004018080602001828103825260288152602001806138b26028913960400191505060405180910390fd5b610d1d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250879250600191506132709050565b60408051602080820190815260048054600260001961010060018416150201909116049383018490526000939092829160609091019084908015612d8e5780601f10612d6357610100808354040283529160200191612d8e565b820191906000526020600020905b815481529060010190602001808311612d7157829003601f168201915b50509250505060405160208183030381529060405280519060200120612db38561166b565b6040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612df3578181015183820152602001612ddb565b50505050905090810190601f168015612e205780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001201415613101576000612e4c8561364a565b9050836040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612e8f578181015183820152602001612e77565b50505050905090810190601f168015612ebc5780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120600182815481101515612ee657fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015612f835780601f10612f5857610100808354040283529160200191612f83565b820191906000526020600020905b815481529060010190602001808311612f6657829003601f168201915b5050925050506040516020818303038152906040528051906020012014806130f95750826040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612fe7578181015183820152602001612fcf565b50505050905090810190601f1680156130145780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561303e57fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156130db5780601f106130b0576101008083540402835291602001916130db565b820191906000526020600020905b8154815290600101906020018083116130be57829003601f168201915b50509250505060405160208183030381529060405280519060200120145b915050611664565b836001600160a01b031660066000856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015613150578181015183820152602001613138565b50505050905090810190601f16801561317d5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b031614806132685750836001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156132065781810151838201526020016131ee565b50505050905090810190601f1680156132335780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316145b949350505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156132bd57600080fd5b505afa1580156132d1573d6000803e3d6000fd5b505050506040513d60208110156132e757600080fd5b50516001600160a01b0316331461333c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60006133478661364a565b6001600160a01b038716600090815260026020526040902054909150156133f9578360018281548110151561337857fe5b9060005260206000209060050201600201908051906020019061339c929190613744565b50826001828154811015156133ad57fe5b906000526020600020906005020160030181905550816001828154811015156133d257fe5b60009182526020909120600590910201600401805460ff1916911515919091179055613514565b600380546001908101918290556001600160a01b03888116600081815260026020908152604080832096909655855160a0810187529283528281018b81529583018a905260608301899052871515608084015284548086018087559590925282517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6600590930292830180546001600160a01b0319169190951617845594518051949592946134d1937fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7909301929190910190613744565b50604082015180516134ed916002840191602090910190613744565b50606082015160038201556080909101516004909101805460ff1916911515919091179055505b7f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776868686858760405180866001600160a01b03166001600160a01b03168152602001806020018060200185151515158152602001848152602001838103835287818151815260200191508051906020019080838360005b838110156135a357818101518382015260200161358b565b50505050905090810190601f1680156135d05780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b838110156136035781810151838201526020016135eb565b50505050905090810190601f1680156136305780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a1505050505050565b6001600160a01b03166000908152600260205260409020546000190190565b6001600160a01b0381166000908152600260205260408120541515613690575060006117bc565b600061369b8361364a565b90506001818154811015156136ac57fe5b906000526020600020906005020160030154915050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106137075782800160ff19823516178555613734565b82800160010185558215613734579182015b82811115613734578235825591602001919060010190613719565b506137409291506137b2565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061378557805160ff1916838001178555613734565b82800160010185558215613734579182015b82811115613734578251825591602001919060010190613797565b61150d91905b8082111561374057600081556001016137b856fe6163636f756e74207265636f76657279206e6f7420696e697469617465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e7420697320616c726561647920626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e2073757370656e646564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e20616374697665207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e6563616e2062652063616c6c656420746f2061737369676e2061646d696e20726f6c6573206f6e6c7963616e6e6f742062652063616c6c65642066726f2061737369676e696e67206f72672061646d696e20616e64206e6574776f726b2061646d696e20726f6c65736163636f756e74206973206e6f7420626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e65737461747573206368616e6765206e6f7420706f737369626c6520666f72206f72672061646d696e206163636f756e7473a165627a7a723058203af90e45bb57e7b90add2e34a684e4b9db8ab4db25d7f2ef46dca296b60e11490029 \ No newline at end of file diff --git a/permission/v1/contract/gen/NodeManager.abi b/permission/v1/contract/gen/NodeManager.abi new file mode 100644 index 0000000000..70e4b62a18 --- /dev/null +++ b/permission/v1/contract/gen/NodeManager.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateNodeStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"enodeId","type":"string"}],"name":"getNodeDetails","outputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_nodeStatus","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_orgId","type":"string"}],"name":"addOrgNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_orgId","type":"string"}],"name":"approveNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeIndex","type":"uint256"}],"name":"getNodeDetailsFromIndex","outputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_nodeStatus","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_orgId","type":"string"}],"name":"addNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfNodes","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_orgId","type":"string"}],"name":"addAdminNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeDeactivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeBlacklisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeRecoveryInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeRecoveryCompleted","type":"event"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/NodeManager.bin b/permission/v1/contract/gen/NodeManager.bin new file mode 100644 index 0000000000..ac304863f8 --- /dev/null +++ b/permission/v1/contract/gen/NodeManager.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5060405160208061250b8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556124a9806100626000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806397c07a9b1161005b57806397c07a9b1461041c578063a97a440614610439578063b81c806a146104f7578063e3b09d84146102a057610088565b80630cc501461461008d5780633f0e0e471461014d5780633f5e1a45146102a057806386bc36521461035e575b600080fd5b61014b600480360360608110156100a357600080fd5b810190602081018135600160201b8111156100bd57600080fd5b8201836020820111156100cf57600080fd5b803590602001918460018302840111600160201b831117156100f057600080fd5b919390929091602081019035600160201b81111561010d57600080fd5b82018360208201111561011f57600080fd5b803590602001918460018302840111600160201b8311171561014057600080fd5b919350915035610511565b005b6101bb6004803603602081101561016357600080fd5b810190602081018135600160201b81111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111600160201b831117156101b057600080fd5b509092509050610f21565b604051808060200180602001848152602001838103835286818151815260200191508051906020019080838360005b838110156102025781810151838201526020016101ea565b50505050905090810190601f16801561022f5780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838360005b8381101561026257818101518382015260200161024a565b50505050905090810190601f16801561028f5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b61014b600480360360408110156102b657600080fd5b810190602081018135600160201b8111156102d057600080fd5b8201836020820111156102e257600080fd5b803590602001918460018302840111600160201b8311171561030357600080fd5b919390929091602081019035600160201b81111561032057600080fd5b82018360208201111561033257600080fd5b803590602001918460018302840111600160201b8311171561035357600080fd5b5090925090506111f7565b61014b6004803603604081101561037457600080fd5b810190602081018135600160201b81111561038e57600080fd5b8201836020820111156103a057600080fd5b803590602001918460018302840111600160201b831117156103c157600080fd5b919390929091602081019035600160201b8111156103de57600080fd5b8201836020820111156103f057600080fd5b803590602001918460018302840111600160201b8311171561041157600080fd5b5090925090506115d0565b6101bb6004803603602081101561043257600080fd5b5035611af2565b61014b6004803603604081101561044f57600080fd5b810190602081018135600160201b81111561046957600080fd5b82018360208201111561047b57600080fd5b803590602001918460018302840111600160201b8311171561049c57600080fd5b919390929091602081019035600160201b8111156104b957600080fd5b8201836020820111156104cb57600080fd5b803590602001918460018302840111600160201b831117156104ec57600080fd5b509092509050611c81565b6104ff61205a565b60408051918252519081900360200190f35b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561055e57600080fd5b505afa158015610572573d6000803e3d6000fd5b505050506040513d602081101561058857600080fd5b50516001600160a01b031633146105dd5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b84848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b8381101561065a578181015183820152602001610642565b50505050905090810190601f1680156106875780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415156107075760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b61077a86868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061206192505050565b15156107ba57604051600160e51b62461bcd02815260040180806020018281038252602a8152602001806123e1602a913960400191505060405180910390fd5b81600114806107c95750816002145b806107d45750816003145b806107df5750816004145b806107ea5750816005145b151561082a57604051600160e51b62461bcd02815260040180806020018281038252602681526020018061242b6026913960400191505060405180910390fd5b81600114156109aa5761087286868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b6002146108b75760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b600360016108fa88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b8154811061090457fe5b9060005260206000209060030201600201819055507fc6c3720fe673e87bb26e06be713d514278aa94c3939cfe7c64b9bea4d486824a868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b8160021415610b2a576109f286868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b600314610a375760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b60026001610a7a88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610a8457fe5b9060005260206000209060030201600201819055507f49796be3ca168a59c8ae46c75a36a9bb3a84753d3e12a812f93ae010e783b14f868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b8160031415610c265760046001610b7688888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610b8057fe5b9060005260206000209060030201600201819055507f4714623279994517c446c8fb72c3fdaca26434da1e2490d3976fe0cd880cfa7a868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b8160041415610da657610c6e86868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b600414610cb35760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b60056001610cf688888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610d0057fe5b9060005260206000209060030201600201819055507ffd385c618a1e89d01fb9a21780846793e282e8bc0b60caf6ccb3e422d543fbfb868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a1610f19565b610de586868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b600514610e2a5760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061240b833981519152604482015290519081900360640190fd5b60026001610e6d88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b81548110610e7757fe5b9060005260206000209060030201600201819055507f787d7bc525e7c4658c64e3e456d974a1be21cc196e8162a4bf1337a12cb38dac868686866040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15b505050505050565b606080600060026000836040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610f6b578181015183820152602001610f53565b50505050905090810190601f168015610f985780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001208152602001908152602001600020546000141561102857848460006040518060200160405280600081525092919082828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509699509197509195506111f0945050505050565b600061106986868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b905060018181548110151561107a57fe5b906000526020600020906003020160010160018281548110151561109a57fe5b90600052602060002090600302016000016001838154811015156110ba57fe5b60009182526020918290206002600390920201810154845460408051601f6000196101006001861615020190931694909404918201859004850284018501905280835290928591908301828280156111535780601f1061112857610100808354040283529160200191611153565b820191906000526020600020905b81548152906001019060200180831161113657829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959850879450925084019050828280156111e15780601f106111b6576101008083540402835291602001916111e1565b820191906000526020600020905b8154815290600101906020018083116111c457829003601f168201915b50505050509150935093509350505b9250925092565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561124457600080fd5b505afa158015611258573d6000803e3d6000fd5b505050506040513d602081101561126e57600080fd5b50516001600160a01b031633146112c35760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b83811015611340578181015183820152602001611328565b50505050905090810190601f16801561136d5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652908501959095525050500160002054156113ec5760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b6003805460010190819055604080516020808201908152918101879052600291600091899189918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506001604051806060016040528087878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250505090825250604080516020601f88018190048102820181019092528681529181019190879087908190840183828082843760009201829052509385525050600260209384015250835460018101808655948252908290208351805160039093029091019261151692849290910190612348565b50602082810151805161152f9260018501920190612348565b50604082015181600201555050507f0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561161d57600080fd5b505afa158015611631573d6000803e3d6000fd5b505050506040513d602081101561164757600080fd5b50516001600160a01b0316331461169c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b83811015611719578181015183820152602001611701565b50505050905090810190601f1680156117465780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415156117c65760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b61183985858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525061206192505050565b151561187957604051600160e51b62461bcd02815260040180806020018281038252602d815260200180612451602d913960400191505060405180910390fd5b6118b885858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c392505050565b60011461190f5760408051600160e51b62461bcd02815260206004820152601c60248201527f6e6f7468696e672070656e64696e6720666f7220617070726f76616c00000000604482015290519081900360640190fd5b600061195086868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506122a092505050565b9050600260018281548110151561196357fe5b9060005260206000209060030201600201819055507f0413ce00d5de406d9939003416263a7530eaeb13f9d281c8baeba1601def960d6001828154811015156119a857fe5b90600052602060002090600302016000016001838154811015156119c857fe5b9060005260206000209060030201600101604051808060200180602001838103835285818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611a665780601f10611a3b57610100808354040283529160200191611a66565b820191906000526020600020905b815481529060010190602001808311611a4957829003601f168201915b5050838103825284546002600019610100600184161502019091160480825260209091019085908015611ada5780601f10611aaf57610100808354040283529160200191611ada565b820191906000526020600020905b815481529060010190602001808311611abd57829003601f168201915b505094505050505060405180910390a1505050505050565b6060806000600184815481101515611b0657fe5b9060005260206000209060030201600101600185815481101515611b2657fe5b9060005260206000209060030201600001600186815481101515611b4657fe5b60009182526020918290206002600390920201810154845460408051601f600019610100600186161502019093169490940491820185900485028401850190528083529092859190830182828015611bdf5780601f10611bb457610100808354040283529160200191611bdf565b820191906000526020600020905b815481529060010190602001808311611bc257829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295985087945092508401905082828015611c6d5780601f10611c4257610100808354040283529160200191611c6d565b820191906000526020600020905b815481529060010190602001808311611c5057829003601f168201915b505050505091509250925092509193909250565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611cce57600080fd5b505afa158015611ce2573d6000803e3d6000fd5b505050506040513d6020811015611cf857600080fd5b50516001600160a01b03163314611d4d5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820181815288519383019390935287516002975093955087945091928392606090920191850190808383895b83811015611dca578181015183820152602001611db2565b50505050905090810190601f168015611df75780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415611e765760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b6003805460010190819055604080516020808201908152918101879052600291600091899189918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506001604051806060016040528087878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250505090825250604080516020601f880181900481028201810190925286815291810191908790879081908401838280828437600092018290525093855250506001602093840181905285549081018087559583529183902084518051600390940290910193611fa093859350910190612348565b506020828101518051611fb99260018501920190612348565b50604082015181600201555050507fb1a7eec7dd1a516c3132d6d1f770758b19aa34c3a07c138caf662688b7e3556f858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b6003545b90565b6000816040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156120a457818101518382015260200161208c565b50505050905090810190601f1680156120d15780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060016120f7856122a0565b8154811061210157fe5b9060005260206000209060030201600101604051602001808060200182810382528381815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561219e5780601f106121735761010080835404028352916020019161219e565b820191906000526020600020905b81548152906001019060200180831161218157829003601f168201915b5050925050506040516020818303038152906040528051906020012014905092915050565b600060026000836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561220a5781810151838201526020016121f2565b50505050905090810190601f1680156122375780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054600014156122715750600061229b565b600161227c836122a0565b8154811061228657fe5b90600052602060002090600302016002015490505b919050565b6000600160026000846040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156122e95781810151838201526020016122d1565b50505050905090810190601f1680156123165780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061238957805160ff19168380011785556123b6565b828001600101855582156123b6579182015b828111156123b657825182559160200191906001019061239b565b506123c29291506123c6565b5090565b61205e91905b808211156123c257600081556001016123cc56fe656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f72676f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000696e76616c6964206f7065726174696f6e2e2077726f6e6720616374696f6e20706173736564656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f7267206964a165627a7a723058207ca0dd787547cf61d1f16df314986310b2a2c8f853fdca9e4a4c784046b0864c0029 \ No newline at end of file diff --git a/permission/v1/contract/gen/OrgManager.abi b/permission/v1/contract/gen/OrgManager.abi new file mode 100644 index 0000000000..50cbf9e810 --- /dev/null +++ b/permission/v1/contract/gen/OrgManager.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateOrg","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"approveOrgStatusUpdate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getUltimateParent","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_pOrgId","type":"string"},{"name":"_orgId","type":"string"}],"name":"addSubOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgIndex","type":"uint256"}],"name":"getOrgInfo","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getSubOrgIndexes","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfOrgs","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"},{"name":"_orgStatus","type":"uint256"}],"name":"checkOrgStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_breadth","type":"uint256"},{"name":"_depth","type":"uint256"}],"name":"setUpOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"}],"name":"approveOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getOrgDetails","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"}],"name":"addOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"checkOrgExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"OrgApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"OrgPendingApproval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"}],"name":"OrgSuspended","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"}],"name":"OrgSuspensionRevoked","type":"event"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/OrgManager.bin b/permission/v1/contract/gen/OrgManager.bin new file mode 100644 index 0000000000..e9f8754c85 --- /dev/null +++ b/permission/v1/contract/gen/OrgManager.bin @@ -0,0 +1 @@ +608060405260018054600160a01b60ff021916905560046002819055600355600060065534801561002f57600080fd5b506040516020806138e98339810180604052602081101561004f57600080fd5b5051600180546001600160a01b0319166001600160a01b0390921691909117905561386a8061007f6000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c80637755ebdd1161008c578063e302831611610066578063e3028316146106c8578063f4d6d9f514610736578063f9953de5146107a4578063ffe40d1d14610812576100cf565b80637755ebdd146105925780638c8642df1461059a5780639e58eb9f14610654576100cf565b80630cc27493146100d457806314f775f914610154578063177c8d8a146101c45780631f953480146102a75780635c4f32ee146103655780635e99f6e5146104d4575b600080fd5b610142600480360360408110156100ea57600080fd5b810190602081018135600160201b81111561010457600080fd5b82018360208201111561011657600080fd5b803590602001918460018302840111600160201b8311171561013757600080fd5b9193509150356108b6565b60408051918252519081900360200190f35b6101c26004803603604081101561016a57600080fd5b810190602081018135600160201b81111561018457600080fd5b82018360208201111561019657600080fd5b803590602001918460018302840111600160201b831117156101b757600080fd5b919350915035610c48565b005b610232600480360360208110156101da57600080fd5b810190602081018135600160201b8111156101f457600080fd5b82018360208201111561020657600080fd5b803590602001918460018302840111600160201b8311171561022757600080fd5b509092509050610e3a565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561026c578181015183820152602001610254565b50505050905090810190601f1680156102995780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101c2600480360360408110156102bd57600080fd5b810190602081018135600160201b8111156102d757600080fd5b8201836020820111156102e957600080fd5b803590602001918460018302840111600160201b8311171561030a57600080fd5b919390929091602081019035600160201b81111561032757600080fd5b82018360208201111561033957600080fd5b803590602001918460018302840111600160201b8311171561035a57600080fd5b509092509050610fef565b6103826004803603602081101561037b57600080fd5b50356111cd565b60405180806020018060200180602001868152602001858152602001848103845289818151815260200191508051906020019080838360005b838110156103d35781810151838201526020016103bb565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b8381101561043357818101518382015260200161041b565b50505050905090810190601f1680156104605780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b8381101561049357818101518382015260200161047b565b50505050905090810190601f1680156104c05780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610542600480360360208110156104ea57600080fd5b810190602081018135600160201b81111561050457600080fd5b82018360208201111561051657600080fd5b803590602001918460018302840111600160201b8311171561053757600080fd5b509092509050611442565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561057e578181015183820152602001610566565b505050509050019250505060405180910390f35b61014261158f565b610640600480360360408110156105b057600080fd5b810190602081018135600160201b8111156105ca57600080fd5b8201836020820111156105dc57600080fd5b803590602001918460018302840111600160201b831117156105fd57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250611596915050565b604080519115158252519081900360200190f35b6101c26004803603606081101561066a57600080fd5b810190602081018135600160201b81111561068457600080fd5b82018360208201111561069657600080fd5b803590602001918460018302840111600160201b831117156106b757600080fd5b9193509150803590602001356116ee565b6101c2600480360360208110156106de57600080fd5b810190602081018135600160201b8111156106f857600080fd5b82018360208201111561070a57600080fd5b803590602001918460018302840111600160201b8311171561072b57600080fd5b50909250905061181a565b6103826004803603602081101561074c57600080fd5b810190602081018135600160201b81111561076657600080fd5b82018360208201111561077857600080fd5b803590602001918460018302840111600160201b8311171561079957600080fd5b509092509050611c25565b6101c2600480360360208110156107ba57600080fd5b810190602081018135600160201b8111156107d457600080fd5b8201836020820111156107e657600080fd5b803590602001918460018302840111600160201b8311171561080757600080fd5b509092509050611f8e565b6106406004803603602081101561082857600080fd5b810190602081018135600160201b81111561084257600080fd5b82018360208201111561085457600080fd5b803590602001918460018302840111600160201b8311171561087557600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061213b945050505050565b60015460408051600160e41b62e32cf902815290516000926001600160a01b031691630e32cf90916004808301926020929190829003018186803b1580156108fd57600080fd5b505afa158015610911573d6000803e3d6000fd5b505050506040513d602081101561092757600080fd5b50516001600160a01b0316331461097c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506109be925083915061213b9050565b1515600114610a0f5760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8260011480610a1e5750826002145b1515610a5e57604051600160e51b62461bcd0281526004018080602001828103825260258152602001806137986025913960400191505060405180910390fd5b6000610a9f86868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b9050600481815481101515610ab057fe5b9060005260206000209060080201600601546001141515610b0557604051600160e51b62461bcd0281526004018080602001828103825260278152602001806137bd6027913960400191505060405180910390fd5b6000808560011415610b1c57506002905080610b2d565b8560021415610b2d57506004905060035b610b6e88888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250869250611596915050565b1515600114610bb157604051600160e51b62461bcd0281526004018080602001828103825260278152602001806137e46027913960400191505060405180910390fd5b8560011415610bfe57610bf988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061225692505050565b610c3d565b610c3d88888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061251892505050565b979650505050505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610c9657600080fd5b505afa158015610caa573d6000803e3d6000fd5b505050506040513d6020811015610cc057600080fd5b50516001600160a01b03163314610d155760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610d57925083915061213b9050565b1515600114610da85760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8160011415610df557610df084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506126c592505050565b610e34565b610e3484848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061298392505050565b50505050565b60015460408051600160e41b62e32cf902815290516060926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610e8157600080fd5b505afa158015610e95573d6000803e3d6000fd5b505050506040513d6020811015610eab57600080fd5b50516001600160a01b03163314610f005760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6004610f4184848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b81548110610f4b57fe5b6000918252602091829020600460089092020101805460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815292830182828015610fe15780601f10610fb657610100808354040283529160200191610fe1565b820191906000526020600020905b815481529060010190602001808311610fc457829003601f168201915b505050505090505b92915050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561103d57600080fd5b505afa158015611051573d6000803e3d6000fd5b505050506040513d602081101561106757600080fd5b50516001600160a01b031633146110bc5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8383838360405160200180858580828437600160f91b601702920191825250600101838380828437808301925050509450505050506040516020818303038152906040526111098161213b565b1561114e5760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b6111c685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525060029250829150612a429050565b5050505050565b60608060606000806004868154811015156111e457fe5b906000526020600020906008020160000160048781548110151561120457fe5b906000526020600020906008020160020160048881548110151561122457fe5b906000526020600020906008020160040160048981548110151561124457fe5b90600052602060002090600802016006015460048a81548110151561126557fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561130c5780601f106112e15761010080835404028352916020019161130c565b820191906000526020600020905b8154815290600101906020018083116112ef57829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a508994509250840190508282801561139a5780601f1061136f5761010080835404028352916020019161139a565b820191906000526020600020905b81548152906001019060200180831161137d57829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156114285780601f106113fd57610100808354040283529160200191611428565b820191906000526020600020905b81548152906001019060200180831161140b57829003601f168201915b505050505092509450945094509450945091939590929450565b606061148383838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061213b92505050565b15156001146114d45760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b600061151584848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b905060048181548110151561152657fe5b906000526020600020906008020160070180548060200260200160405190810160405280929190818152602001828054801561158157602002820191906000526020600020905b81548152602001906001019080831161156d575b505050505091505092915050565b6004545b90565b600060056000846040516020018082805190602001908083835b602083106115cf5780518252601f1990920191602091820191016115b0565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001208152602001908152602001600020546000141561162957506000610fe9565b6000611634846121c9565b905060056000856040516020018082805190602001908083835b6020831061166d5780518252601f19909201916020918201910161164e565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001208152602001908152602001600020546000141580156116e65750826004828154811015156116d257fe5b906000526020600020906008020160010154145b949350505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561173c57600080fd5b505afa158015611750573d6000803e3d6000fd5b505050506040513d602081101561176657600080fd5b50516001600160a01b031633146117bb5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6118106040518060200160405280600081525085858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506001925060029150612a429050565b6002556003555050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561186857600080fd5b505afa15801561187c573d6000803e3d6000fd5b505050506040513d602081101561189257600080fd5b50516001600160a01b031633146118e75760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b61192982828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250611596915050565b151560011461197a5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b60006119bb83838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b905060026004828154811015156119ce57fe5b9060005260206000209060080201600101819055507fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c600482815481101515611a1357fe5b9060005260206000209060080201600001600483815481101515611a3357fe5b9060005260206000209060080201600201600484815481101515611a5357fe5b9060005260206000209060080201600401600485815481101515611a7357fe5b906000526020600020906008020160060154600260405180806020018060200180602001868152602001858152602001848103845289818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611b245780601f10611af957610100808354040283529160200191611b24565b820191906000526020600020905b815481529060010190602001808311611b0757829003601f168201915b5050848103835288546002600019610100600184161502019091160480825260209091019089908015611b985780601f10611b6d57610100808354040283529160200191611b98565b820191906000526020600020905b815481529060010190602001808311611b7b57829003601f168201915b5050848103825287546002600019610100600184161502019091160480825260209091019088908015611c0c5780601f10611be157610100808354040283529160200191611c0c565b820191906000526020600020905b815481529060010190602001808311611bef57829003601f168201915b50509850505050505050505060405180910390a1505050565b6060806060600080611c6c87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061213b92505050565b1515611cdb57868660008083838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820183528382528251908101909252918152949d509b50929950939750919550611f84945050505050565b6000611d1c88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121c992505050565b9050600481815481101515611d2d57fe5b9060005260206000209060080201600001600482815481101515611d4d57fe5b9060005260206000209060080201600201600483815481101515611d6d57fe5b9060005260206000209060080201600401600484815481101515611d8d57fe5b906000526020600020906008020160060154600485815481101515611dae57fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611e555780601f10611e2a57610100808354040283529160200191611e55565b820191906000526020600020905b815481529060010190602001808311611e3857829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a5089945092508401905082828015611ee35780601f10611eb857610100808354040283529160200191611ee3565b820191906000526020600020905b815481529060010190602001808311611ec657829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295995088945092508401905082828015611f715780601f10611f4657610100808354040283529160200191611f71565b820191906000526020600020905b815481529060010190602001808311611f5457829003601f168201915b5050505050925095509550955095509550505b9295509295909350565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611fdc57600080fd5b505afa158015611ff0573d6000803e3d6000fd5b505050506040513d602081101561200657600080fd5b50516001600160a01b0316331461205b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b81818080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061209d925083915061213b9050565b156120e25760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b6121366040518060200160405280600081525084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250829150612a429050565b505050565b600060056000836040516020018082805190602001908083835b602083106121745780518252601f199092019160209182019101612155565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014159050919050565b6000600160056000846040516020018082805190602001908083835b602083106122045780518252601f1990920191602091820191016121e5565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b612261816002611596565b15156001146122a457604051600160e51b62461bcd02815260040180806020018281038252603481526020018061380b6034913960400191505060405180910390fd5b60006122af826121c9565b905060036004828154811015156122c257fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b60048281548110151561230757fe5b906000526020600020906008020160000160048381548110151561232757fe5b906000526020600020906008020160020160048481548110151561234757fe5b906000526020600020906008020160040160048581548110151561236757fe5b9060005260206000209060080201600601546003604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156124185780601f106123ed57610100808354040283529160200191612418565b820191906000526020600020905b8154815290600101906020018083116123fb57829003601f168201915b505084810383528854600260001961010060018416150201909116048082526020909101908990801561248c5780601f106124615761010080835404028352916020019161248c565b820191906000526020600020905b81548152906001019060200180831161246f57829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156125005780601f106124d557610100808354040283529160200191612500565b820191906000526020600020905b8154815290600101906020018083116124e357829003601f168201915b50509850505050505050505060405180910390a15050565b612523816004611596565b151560011461257c5760408051600160e51b62461bcd02815260206004820152601a60248201527f6f7267206e6f7420696e2073757370656e646564207374617465000000000000604482015290519081900360640190fd5b6000612587826121c9565b9050600560048281548110151561259a57fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156125df57fe5b90600052602060002090600802016000016004838154811015156125ff57fe5b906000526020600020906008020160020160048481548110151561261f57fe5b906000526020600020906008020160040160048581548110151561263f57fe5b9060005260206000209060080201600601546005604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156124185780601f106123ed57610100808354040283529160200191612418565b6126d0816003611596565b15156001146127215760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b600061272c826121c9565b90506004808281548110151561273e57fe5b9060005260206000209060080201600101819055507f73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d9660048281548110151561278357fe5b90600052602060002090600802016000016004838154811015156127a357fe5b90600052602060002090600802016002016004848154811015156127c357fe5b90600052602060002090600802016004016004858154811015156127e357fe5b600091825260209182902060066008909202010154604080516060810183905260808082528754600260001961010060018416150201909116049082018190529293909283929183019183019060a0840190899080156128845780601f1061285957610100808354040283529160200191612884565b820191906000526020600020905b81548152906001019060200180831161286757829003601f168201915b50508481038352875460026000196101006001841615020190911604808252602090910190889080156128f85780601f106128cd576101008083540402835291602001916128f8565b820191906000526020600020905b8154815290600101906020018083116128db57829003601f168201915b505084810382528654600260001961010060018416150201909116048082526020909101908790801561296c5780601f106129415761010080835404028352916020019161296c565b820191906000526020600020905b81548152906001019060200180831161294f57829003601f168201915b505097505050505050505060405180910390a15050565b61298e816005611596565b15156001146129df5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b60006129ea826121c9565b905060026004828154811015156129fd57fe5b9060005260206000209060080201600101819055507f882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f60048281548110151561278357fe5b600080806001851415612ac457856040516020018082805190602001908083835b60208310612a825780518252601f199092019160209182019101612a63565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001209150612c03565b866040516020018082805190602001908083835b60208310612af75780518252601f199092019160209182019101612ad8565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120925086866040516020018083805190602001908083835b60208310612b685780518252601f199092019160209182019101612b49565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b60208310612bc45780518252601f199092019160209182019101612ba5565b6001836020036101000a038019825116818451168082178552505050505050905001925050506040516020818303038152906040528051906020012091505b600680546001908101918290556000848152600560205260408120929092556004805491612c339190830161355e565b90508560011415612cf85785600482815481101515612c4e57fe5b9060005260206000209060080201600601819055506000600482815481101515612c7457fe5b90600052602060002090600802016005018190555086600482815481101515612c9957fe5b90600052602060002090600802016003019080519060200190612cbd92919061358a565b5086600482815481101515612cce57fe5b90600052602060002090600802016004019080519060200190612cf292919061358a565b5061303c565b600084815260056020526040902054600354600480546000199093019450909184908110612d2257fe5b600091825260209091206007600890920201015410612d8b5760408051600160e51b62461bcd02815260206004820152601660248201527f62726561647468206c6576656c20657863656564656400000000000000000000604482015290519081900360640190fd5b6002546004805484908110612d9c57fe5b906000526020600020906008020160060154101515612e055760408051600160e51b62461bcd02815260206004820152601460248201527f6465707468206c6576656c206578636565646564000000000000000000000000604482015290519081900360640190fd5b6004805483908110612e1357fe5b906000526020600020906008020160060154600101600482815481101515612e3757fe5b90600052602060002090600802016006018190555081600482815481101515612e5c57fe5b60009182526020909120600560089092020101556004805483908110612e7e57fe5b9060005260206000209060080201600401600482815481101515612e9e57fe5b90600052602060002090600802016004019080546001816001161561010002031660029004612ece929190613608565b506000600483815481101515612ee057fe5b90600052602060002090600802016007018054809190600101612f03919061367d565b905081600484815481101515612f1557fe5b906000526020600020906008020160070182815481101515612f3357fe5b906000526020600020018190555088886040516020018083805190602001908083835b60208310612f755780518252601f199092019160209182019101612f56565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b60208310612fd15780518252601f199092019160209182019101612fb2565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405260048381548110151561301557fe5b9060005260206000209060080201600301908051906020019061303992919061358a565b50505b8660048281548110151561304c57fe5b9060005260206000209060080201600001908051906020019061307092919061358a565b508760048281548110151561308157fe5b906000526020600020906008020160020190805190602001906130a592919061358a565b50846004828154811015156130b657fe5b9060005260206000209060080201600101819055508460011415613316577f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b60048281548110151561310457fe5b906000526020600020906008020160000160048381548110151561312457fe5b906000526020600020906008020160020160048481548110151561314457fe5b906000526020600020906008020160040160048581548110151561316457fe5b9060005260206000209060080201600601546001604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156132155780601f106131ea57610100808354040283529160200191613215565b820191906000526020600020905b8154815290600101906020018083116131f857829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156132895780601f1061325e57610100808354040283529160200191613289565b820191906000526020600020905b81548152906001019060200180831161326c57829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156132fd5780601f106132d2576101008083540402835291602001916132fd565b820191906000526020600020905b8154815290600101906020018083116132e057829003601f168201915b50509850505050505050505060405180910390a1613554565b7fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c60048281548110151561334657fe5b906000526020600020906008020160000160048381548110151561336657fe5b906000526020600020906008020160020160048481548110151561338657fe5b90600052602060002090600802016004016004858154811015156133a657fe5b9060005260206000209060080201600601546002604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156134575780601f1061342c57610100808354040283529160200191613457565b820191906000526020600020905b81548152906001019060200180831161343a57829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156134cb5780601f106134a0576101008083540402835291602001916134cb565b820191906000526020600020905b8154815290600101906020018083116134ae57829003601f168201915b505084810382528754600260001961010060018416150201909116048082526020909101908890801561353f5780601f106135145761010080835404028352916020019161353f565b820191906000526020600020905b81548152906001019060200180831161352257829003601f168201915b50509850505050505050505060405180910390a15b5050505050505050565b8154818355818111156121365760080281600802836000526020600020918201910161213691906136a1565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106135cb57805160ff19168380011785556135f8565b828001600101855582156135f8579182015b828111156135f85782518255916020019190600101906135dd565b50613604929150613718565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061364157805485556135f8565b828001600101855582156135f857600052602060002091601f016020900482015b828111156135f8578254825591600101919060010190613662565b81548183558181111561213657600083815260209020612136918101908301613718565b61159391905b808211156136045760006136bb8282613732565b60018201600090556002820160006136d39190613732565b6136e1600383016000613732565b6136ef600483016000613732565b6005820160009055600682016000905560078201600061370f9190613779565b506008016136a7565b61159391905b80821115613604576000815560010161371e565b50805460018160011615610100020316600290046000825580601f106137585750613776565b601f0160209004906000526020600020908101906137769190613718565b50565b5080546000825590600052602060002090810190613776919061371856fe696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f7765646e6f742061206d6173746572206f72672e206f7065726174696f6e206e6f7420616c6c6f7765646f72672073746174757320646f6573206e6f7420616c6c6f7720746865206f7065726174696f6e6f7267206e6f7420696e20617070726f766564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e65a165627a7a72305820a443b9df85904ce8fcc8f004110f5465352bd5d4804a4e22547ef1fad4e8f21b0029 \ No newline at end of file diff --git a/permission/v1/contract/gen/PermissionsImplementation.abi b/permission/v1/contract/gen/PermissionsImplementation.abi new file mode 100644 index 0000000000..03cffdab7e --- /dev/null +++ b/permission/v1/contract/gen/PermissionsImplementation.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"updateAccountStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_access","type":"uint256"},{"name":"_voter","type":"bool"},{"name":"_admin","type":"bool"},{"name":"_caller","type":"address"}],"name":"addNewRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminOrg","type":"string"},{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"}],"name":"setPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"startBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"approveOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"updateOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"}],"name":"addAdminNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_roleId","type":"string"},{"name":"_caller","type":"address"}],"name":"assignAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"approveBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"}],"name":"addAdminAccount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_caller","type":"address"}],"name":"addNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_caller","type":"address"}],"name":"removeRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_caller","type":"address"}],"name":"approveBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"validateAccount","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"approveAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"},{"name":"_caller","type":"address"}],"name":"assignAccountRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"isOrgAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_breadth","type":"uint256"},{"name":"_depth","type":"uint256"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_pOrgId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_caller","type":"address"}],"name":"addSubOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"approveOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_caller","type":"address"}],"name":"startBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPolicyDetails","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"isNetworkAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"updateNodeStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getPendingOp","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminOrg","type":"string"},{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"},{"name":"_networkBootStatus","type":"bool"}],"name":"setMigrationPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"addOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"},{"name":"_orgManager","type":"address"},{"name":"_rolesManager","type":"address"},{"name":"_accountManager","type":"address"},{"name":"_voterManager","type":"address"},{"name":"_nodeManager","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_networkBootStatus","type":"bool"}],"name":"PermissionsInitialized","type":"event"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/PermissionsImplementation.bin b/permission/v1/contract/gen/PermissionsImplementation.bin new file mode 100644 index 0000000000..4ed336e94b --- /dev/null +++ b/permission/v1/contract/gen/PermissionsImplementation.bin @@ -0,0 +1 @@ +60806040526003600955600a805460ff191690553480156200002057600080fd5b5060405160c08062007181833981018060405260c08110156200004257600080fd5b508051602082015160408301516060840151608085015160a090950151600580546001600160a01b039687166001600160a01b03199182161790915560048054958716958216959095179094556001805493861693851693909317909255600080549185169184169190911790556002805494841694831694909417909355600380549290931691161790556170a380620000de6000396000f3fe608060405234801561001057600080fd5b50600436106101cf5760003560e01c8063655a8ef511610104578063b5546564116100a2578063dbfad71111610071578063dbfad71114611196578063f346a3a714611263578063f5ad584a146113cf578063f922f802146114df576101cf565b8063b554656414610ed8578063c3dc8e0914610f55578063cc9ba6fa1461101c578063d1aa0c2014611170576101cf565b80638baa8191116100de5780638baa819114610ba65780639bd3810114610cea578063a5843f0814610d9e578063a64d286014610dc1576101cf565b8063655a8ef5146109ac5780636b568d7614610a735780638884304114610b27576101cf565b8063404bf3eb116101715780634cbfa82e1161014b5780634cbfa82e146107f05780634fe57e7a146107f857806359a260a31461081e5780635ca5adbe146108e5576101cf565b8063404bf3eb1461068157806344478e79146107555780634b20f45f14610771576101cf565b80631c249912116101ad5780631c249912146104485780633bc07dea146104c75780633cf5f33b146105965780633f25c28814610613576101cf565b806304e81f1e146101d45780631b04c2761461025d5780631b6102201461033a575b600080fd5b61025b600480360360808110156101ea57600080fd5b810190602081018135600160201b81111561020457600080fd5b82018360208201111561021657600080fd5b803590602001918460018302840111600160201b8311171561023757600080fd5b91935091506001600160a01b038135811691602081013591604090910135166115ae565b005b61025b600480360360c081101561027357600080fd5b810190602081018135600160201b81111561028d57600080fd5b82018360208201111561029f57600080fd5b803590602001918460018302840111600160201b831117156102c057600080fd5b919390929091602081019035600160201b8111156102dd57600080fd5b8201836020820111156102ef57600080fd5b803590602001918460018302840111600160201b8311171561031057600080fd5b919350915080359060208101351515906040810135151590606001356001600160a01b0316611801565b61025b6004803603606081101561035057600080fd5b810190602081018135600160201b81111561036a57600080fd5b82018360208201111561037c57600080fd5b803590602001918460018302840111600160201b8311171561039d57600080fd5b919390929091602081019035600160201b8111156103ba57600080fd5b8201836020820111156103cc57600080fd5b803590602001918460018302840111600160201b831117156103ed57600080fd5b919390929091602081019035600160201b81111561040a57600080fd5b82018360208201111561041c57600080fd5b803590602001918460018302840111600160201b8311171561043d57600080fd5b509092509050611ac3565b61025b6004803603606081101561045e57600080fd5b810190602081018135600160201b81111561047857600080fd5b82018360208201111561048a57600080fd5b803590602001918460018302840111600160201b831117156104ab57600080fd5b91935091506001600160a01b0381358116916020013516611c02565b61025b600480360360808110156104dd57600080fd5b810190602081018135600160201b8111156104f757600080fd5b82018360208201111561050957600080fd5b803590602001918460018302840111600160201b8311171561052a57600080fd5b919390929091602081019035600160201b81111561054757600080fd5b82018360208201111561055957600080fd5b803590602001918460018302840111600160201b8311171561057a57600080fd5b91935091506001600160a01b0381358116916020013516611f01565b61025b600480360360608110156105ac57600080fd5b810190602081018135600160201b8111156105c657600080fd5b8201836020820111156105d857600080fd5b803590602001918460018302840111600160201b831117156105f957600080fd5b9193509150803590602001356001600160a01b0316612487565b61025b6004803603602081101561062957600080fd5b810190602081018135600160201b81111561064357600080fd5b82018360208201111561065557600080fd5b803590602001918460018302840111600160201b8311171561067657600080fd5b50909250905061278f565b61025b6004803603608081101561069757600080fd5b810190602081018135600160201b8111156106b157600080fd5b8201836020820111156106c357600080fd5b803590602001918460018302840111600160201b831117156106e457600080fd5b919390926001600160a01b0383351692604081019060200135600160201b81111561070e57600080fd5b82018360208201111561072057600080fd5b803590602001918460018302840111600160201b8311171561074157600080fd5b9193509150356001600160a01b03166129a3565b61075d612d62565b604080519115158252519081900360200190f35b61025b6004803603606081101561078757600080fd5b810190602081018135600160201b8111156107a157600080fd5b8201836020820111156107b357600080fd5b803590602001918460018302840111600160201b831117156107d457600080fd5b91935091506001600160a01b0381358116916020013516612ec0565b61075d613109565b61025b6004803603602081101561080e57600080fd5b50356001600160a01b0316613113565b61025b6004803603606081101561083457600080fd5b810190602081018135600160201b81111561084e57600080fd5b82018360208201111561086057600080fd5b803590602001918460018302840111600160201b8311171561088157600080fd5b919390929091602081019035600160201b81111561089e57600080fd5b8201836020820111156108b057600080fd5b803590602001918460018302840111600160201b831117156108d157600080fd5b9193509150356001600160a01b0316613434565b61025b600480360360608110156108fb57600080fd5b810190602081018135600160201b81111561091557600080fd5b82018360208201111561092757600080fd5b803590602001918460018302840111600160201b8311171561094857600080fd5b919390929091602081019035600160201b81111561096557600080fd5b82018360208201111561097757600080fd5b803590602001918460018302840111600160201b8311171561099857600080fd5b9193509150356001600160a01b03166136b0565b61025b600480360360608110156109c257600080fd5b810190602081018135600160201b8111156109dc57600080fd5b8201836020820111156109ee57600080fd5b803590602001918460018302840111600160201b83111715610a0f57600080fd5b919390929091602081019035600160201b811115610a2c57600080fd5b820183602082011115610a3e57600080fd5b803590602001918460018302840111600160201b83111715610a5f57600080fd5b9193509150356001600160a01b0316613b72565b61075d60048036036040811015610a8957600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610ab357600080fd5b820183602082011115610ac557600080fd5b803590602001918460018302840111600160201b83111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613dd6945050505050565b61025b60048036036060811015610b3d57600080fd5b810190602081018135600160201b811115610b5757600080fd5b820183602082011115610b6957600080fd5b803590602001918460018302840111600160201b83111715610b8a57600080fd5b91935091506001600160a01b0381358116916020013516613ec4565b61025b60048036036080811015610bbc57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610be657600080fd5b820183602082011115610bf857600080fd5b803590602001918460018302840111600160201b83111715610c1957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610c6b57600080fd5b820183602082011115610c7d57600080fd5b803590602001918460018302840111600160201b83111715610c9e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506143199050565b61075d60048036036040811015610d0057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610d2a57600080fd5b820183602082011115610d3c57600080fd5b803590602001918460018302840111600160201b83111715610d5d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550614827945050505050565b61025b60048036036040811015610db457600080fd5b5080359060200135614bdf565b61025b60048036036080811015610dd757600080fd5b810190602081018135600160201b811115610df157600080fd5b820183602082011115610e0357600080fd5b803590602001918460018302840111600160201b83111715610e2457600080fd5b919390929091602081019035600160201b811115610e4157600080fd5b820183602082011115610e5357600080fd5b803590602001918460018302840111600160201b83111715610e7457600080fd5b919390929091602081019035600160201b811115610e9157600080fd5b820183602082011115610ea357600080fd5b803590602001918460018302840111600160201b83111715610ec457600080fd5b9193509150356001600160a01b031661509a565b61025b60048036036060811015610eee57600080fd5b810190602081018135600160201b811115610f0857600080fd5b820183602082011115610f1a57600080fd5b803590602001918460018302840111600160201b83111715610f3b57600080fd5b9193509150803590602001356001600160a01b0316615468565b61025b60048036036060811015610f6b57600080fd5b810190602081018135600160201b811115610f8557600080fd5b820183602082011115610f9757600080fd5b803590602001918460018302840111600160201b83111715610fb857600080fd5b919390929091602081019035600160201b811115610fd557600080fd5b820183602082011115610fe757600080fd5b803590602001918460018302840111600160201b8311171561100857600080fd5b9193509150356001600160a01b03166157c1565b611024615add565b604080518215156060820152608080825286519082015285519091829160208084019284019160a08501918a019080838360005b83811015611070578181015183820152602001611058565b50505050905090810190601f16801561109d5780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156110d05781810151838201526020016110b8565b50505050905090810190601f1680156110fd5780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b83811015611130578181015183820152602001611118565b50505050905090810190601f16801561115d5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b61075d6004803603602081101561118657600080fd5b50356001600160a01b0316615cb0565b61025b600480360360808110156111ac57600080fd5b810190602081018135600160201b8111156111c657600080fd5b8201836020820111156111d857600080fd5b803590602001918460018302840111600160201b831117156111f957600080fd5b919390929091602081019035600160201b81111561121657600080fd5b82018360208201111561122857600080fd5b803590602001918460018302840111600160201b8311171561124957600080fd5b9193509150803590602001356001600160a01b0316615eb4565b6112d16004803603602081101561127957600080fd5b810190602081018135600160201b81111561129357600080fd5b8201836020820111156112a557600080fd5b803590602001918460018302840111600160201b831117156112c657600080fd5b50909250905061610c565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015611330578181015183820152602001611318565b50505050905090810190601f16801561135d5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015611390578181015183820152602001611378565b50505050905090810190601f1680156113bd5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61025b600480360360808110156113e557600080fd5b810190602081018135600160201b8111156113ff57600080fd5b82018360208201111561141157600080fd5b803590602001918460018302840111600160201b8311171561143257600080fd5b919390929091602081019035600160201b81111561144f57600080fd5b82018360208201111561146157600080fd5b803590602001918460018302840111600160201b8311171561148257600080fd5b919390929091602081019035600160201b81111561149f57600080fd5b8201836020820111156114b157600080fd5b803590602001918460018302840111600160201b831117156114d257600080fd5b9193509150351515616274565b61025b600480360360808110156114f557600080fd5b810190602081018135600160201b81111561150f57600080fd5b82018360208201111561152157600080fd5b803590602001918460018302840111600160201b8311171561154257600080fd5b919390929091602081019035600160201b81111561155f57600080fd5b82018360208201111561157157600080fd5b803590602001918460018302840111600160201b8311171561159257600080fd5b91935091506001600160a01b0381358116916020013516616363565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156115fc57600080fd5b505afa158015611610573d6000803e3d6000fd5b505050506040513d602081101561162657600080fd5b50516001600160a01b0316331461167157604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8085858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506116b59250849150839050614827565b15156001146116f857604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b83600114806117075750836002145b806117125750836003145b151561175257604051600160e51b62461bcd028152600401808060200182810382526025815260200180616fe36025913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03878116602483015260448201879052606060048301908152606483018a90529216916384b7a84a918a918a918a918a918190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117e057600080fd5b505af11580156117f4573d6000803e3d6000fd5b5050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561184f57600080fd5b505afa158015611863573d6000803e3d6000fd5b505050506040513d602081101561187957600080fd5b50516001600160a01b031633146118c457604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611906925083915061690c9050565b151560011461194d5760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b8187878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506119919250849150839050614827565b15156001146119d457604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b600154604051600160e01b637b713579028152604481018990528715156064820152861515608482015260a06004820190815260a482018d90526001600160a01b0390921691637b713579918e918e918e918e918e918e918e91908190602481019060c4018a8a80828437600083820152601f01601f191690910184810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b158015611a9e57600080fd5b505af1158015611ab2573d6000803e3d6000fd5b505050505050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611b1157600080fd5b505afa158015611b25573d6000803e3d6000fd5b505050506040513d6020811015611b3b57600080fd5b50516001600160a01b03163314611b8657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff1615611bd25760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b611bde60068888616f14565b50611beb60078686616f14565b50611bf860088484616f14565b5050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611c5057600080fd5b505afa158015611c64573d6000803e3d6000fd5b505050506040513d6020811015611c7a57600080fd5b50516001600160a01b03163314611cc557604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80611ccf81615cb0565b1515600114611d1257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03858116602483015260046044830181905260608382019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611da457600080fd5b505af1158015611db8573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b03888116606483015260066084830181905260a060048401908152815460001960018216156101000201169590950460a4840181905291909316955063e98ac22d945091928a928a928a928692909182916024820191604481019160c49091019086908015611e855780601f10611e5a57610100808354040283529160200191611e85565b820191906000526020600020905b815481529060010190602001808311611e6857829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015611ee257600080fd5b505af1158015611ef6573d6000803e3d6000fd5b505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611f4f57600080fd5b505afa158015611f63573d6000803e3d6000fd5b505050506040513d6020811015611f7957600080fd5b50516001600160a01b03163314611fc457604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80611fce81615cb0565b151560011461201157604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b61205387878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506169fb915050565b15156001146120ac5760408051600160e51b62461bcd02815260206004820152601260248201527f4e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261214693909290918301828280156121395780601f1061210e57610100808354040283529160200191612139565b820191906000526020600020905b81548152906001019060200180831161211c57829003601f168201915b5050505050836001616a68565b1561247e5760048054604051600160e11b637181418b0281526020928101928352602481018990526001600160a01b039091169163e3028316918a918a918190604401848480828437600081840152601f19601f8201169050808301925050509350505050600060405180830381600087803b1580156121c557600080fd5b505af11580156121d9573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526008805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b7135799650948e948e94939192839290918291602481019160c4909101908a9080156122a95780601f1061227e576101008083540402835291602001916122a9565b820191906000526020600020905b81548152906001019060200180831161228c57829003601f168201915b50508381038252878152602001888880828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156122f757600080fd5b505af115801561230b573d6000803e3d6000fd5b505060035460408051600160e11b63435e1b2902815260048101918252604481018990526001600160a01b0390921693506386bc36529250889188918c918c919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156123b857600080fd5b505af11580156123cc573d6000803e3d6000fd5b505060005460408051600160e01b63c214e5e50281526001600160a01b03888116602483015260048201928352604482018c9052909216935063c214e5e592508a918a9188918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b15801561245457600080fd5b505af1158015612468573d6000803e3d6000fd5b505050506040513d6020811015611ef657600080fd5b50505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156124d557600080fd5b505afa1580156124e9573d6000803e3d6000fd5b505050506040513d60208110156124ff57600080fd5b50516001600160a01b0316331461254a57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8061255481615cb0565b151560011461259757604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b6004805460408051600160e01b630cc2749302815260248101879052928301908152604483018790526000926001600160a01b0390921691630cc27493918991899189918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b15801561261d57600080fd5b505af1158015612631573d6000803e3d6000fd5b505050506040513d602081101561264757600080fd5b505160028054604051600160e01b63e98ac22d0281526000606482018190526084820185905260a0600483019081526006805460001960018216156101000201169590950460a484018190529596506001600160a01b039093169463e98ac22d94938c938c939289929182916024820191604481019160c4909101908a9080156127125780601f106126e757610100808354040283529160200191612712565b820191906000526020600020905b8154815290600101906020018083116126f557829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b15801561276f57600080fd5b505af1158015612783573d6000803e3d6000fd5b50505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156127dd57600080fd5b505afa1580156127f1573d6000803e3d6000fd5b505050506040513d602081101561280757600080fd5b50516001600160a01b0316331461285257604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff161561289e5760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b60035460408051600160e21b6338ec276102815260048101918252604481018590526001600160a01b039092169163e3b09d84918691869160069181906024810190606401868680828437600083820152601f01601f19169091018481038352855460026000196101006001841615020190911604808252602090910191508590801561296c5780601f106129415761010080835404028352916020019161296c565b820191906000526020600020905b81548152906001019060200180831161294f57829003601f168201915b505095505050505050600060405180830381600087803b15801561298f57600080fd5b505af115801561247e573d6000803e3d6000fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156129f157600080fd5b505afa158015612a05573d6000803e3d6000fd5b505050506040513d6020811015612a1b57600080fd5b50516001600160a01b03163314612a6657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612aa89250839150616b629050565b1515600114612af95760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b81612b0381615cb0565b1515600114612b4657604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b600054604051600160e01b63e3483a9d0281526001600160a01b0388811660048301908152600160648401819052608060248501908152608485018d9052929094169363e3483a9d938b938e938e938d938d9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b158015612c0957600080fd5b505af1158015612c1d573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b038b8116606483015260046084830181905260a08382019081526006805460001960018216156101000201169690960460a4850181905292909416965063e98ac22d95508e938e938e9382916024810191604482019160c401908a908015612ce35780601f10612cb857610100808354040283529160200191612ce3565b820191906000526020600020905b815481529060010190602001808311612cc657829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015612d4057600080fd5b505af1158015612d54573d6000803e3d6000fd5b505050505050505050505050565b60055460408051600160e21b63395c945702815290516000926001600160a01b03169163e572515c916004808301926020929190829003018186803b158015612daa57600080fd5b505afa158015612dbe573d6000803e3d6000fd5b505050506040513d6020811015612dd457600080fd5b50516001600160a01b03163314612e1f57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff1615612e6b5760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b600a805460ff1916600117908190556040805160ff9290921615158252517f04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf9181900360200190a1600a5460ff1691505b5090565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f0e57600080fd5b505afa158015612f22573d6000803e3d6000fd5b505050506040513d6020811015612f3857600080fd5b50516001600160a01b03163314612f8357604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80612f8d81615cb0565b1515600114612fd057604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261306a939092909183018282801561305d5780601f106130325761010080835404028352916020019161305d565b820191906000526020600020905b81548152906001019060200180831161304057829003601f168201915b5050505050836006616a68565b1561310257600054604051600160e11b63425bd4250281526001600160a01b0385811660248301526005604483018190526060600484019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611ee257600080fd5b5050505050565b600a5460ff165b90565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561316157600080fd5b505afa158015613175573d6000803e3d6000fd5b505050506040513d602081101561318b57600080fd5b50516001600160a01b031633146131d657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff16156132225760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526132bc93909290918301828280156132af5780601f10613284576101008083540402835291602001916132af565b820191906000526020600020905b81548152906001019060200180831161329257829003601f168201915b5050505050836001616c27565b600054604051600160e01b63e3483a9d0281526001600160a01b038481166004830190815260026064840181905260806024850190815260068054600019600182161561010002011683900460848701819052949096169563e3483a9d95899591946007949390929091604481019160a490910190879080156133805780601f1061335557610100808354040283529160200191613380565b820191906000526020600020905b81548152906001019060200180831161336357829003601f168201915b50508381038252855460026000196101006001841615020190911604808252602090910190869080156133f45780601f106133c9576101008083540402835291602001916133f4565b820191906000526020600020905b8154815290600101906020018083116133d757829003601f168201915b50509650505050505050600060405180830381600087803b15801561341857600080fd5b505af115801561342c573d6000803e3d6000fd5b505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561348257600080fd5b505afa158015613496573d6000803e3d6000fd5b505050506040513d60208110156134ac57600080fd5b50516001600160a01b031633146134f757604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b84848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613539925083915061690c9050565b15156001146135805760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b8186868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506135c49250849150839050614827565b151560011461360757604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b60035460408051600160e01b633f5e1a4502815260048101918252604481018890526001600160a01b0390921691633f5e1a4591899189918d918d919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612d4057600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156136fe57600080fd5b505afa158015613712573d6000803e3d6000fd5b505050506040513d602081101561372857600080fd5b50516001600160a01b0316331461377357604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506137b5925083915061690c9050565b15156001146137fc5760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b8184848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506138409250849150839050614827565b151560011461388357604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b60408051602080820190815260078054600260001961010060018416150201909116049383018490529290918291606090910190849080156139065780601f106138db57610100808354040283529160200191613906565b820191906000526020600020905b8154815290600101906020018083116138e957829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014158015613a7357506040805160208082019081526008805460026000196101006001841615020190911604938301849052929091829160609091019084908015613a015780601f106139d657610100808354040283529160200191613a01565b820191906000526020600020905b8154815290600101906020018083116139e457829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014155b1515613ac95760408051600160e51b62461bcd02815260206004820152601d60248201527f61646d696e20726f6c65732063616e6e6f742062652072656d6f766564000000604482015290519081900360640190fd5b60015460408051600160e11b63531a180902815260048101918252604481018a90526001600160a01b039092169163a6343012918b918b918b918b919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612d4057600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015613bc057600080fd5b505afa158015613bd4573d6000803e3d6000fd5b505050506040513d6020811015613bea57600080fd5b50516001600160a01b03163314613c3557604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80613c3f81615cb0565b1515600114613c8257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152613d1c9390929091830182828015613d0f5780601f10613ce457610100808354040283529160200191613d0f565b820191906000526020600020905b815481529060010190602001808311613cf257829003601f168201915b5050505050836005616a68565b1561342c57600354604051600160e11b63066280a3028152600560448201819052606060048301908152606483018790526001600160a01b0390931692630cc5014692889288928c928c92919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b15801561276f57600080fd5b6000805460408051600160e11b6335ab46bb0281526001600160a01b0386811660048301908152602483019384528651604484015286519190941693636b568d76938893889360649091019060208501908083838c5b83811015613e44578181015183820152602001613e2c565b50505050905090810190601f168015613e715780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015613e8f57600080fd5b505afa158015613ea3573d6000803e3d6000fd5b505050506040513d6020811015613eb957600080fd5b505190505b92915050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015613f1257600080fd5b505afa158015613f26573d6000803e3d6000fd5b505050506040513d6020811015613f3c57600080fd5b50516001600160a01b03163314613f8757604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80613f9181615cb0565b1515600114613fd457604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261406e93909290918301828280156140615780601f1061403657610100808354040283529160200191614061565b820191906000526020600020905b81548152906001019060200180831161404457829003601f168201915b5050505050836004616a68565b156131025760008054604051600160e01b631d09dc930281526020600482019081526024820188905283926001600160a01b031691631d09dc93918a918a91908190604401848480828437600081840152601f19601f82011690508083019250505093505050506040805180830381600087803b1580156140ee57600080fd5b505af1158015614102573d6000803e3d6000fd5b505050506040513d604081101561411857600080fd5b508051602090910151909250905081156141c65760068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526141c693909290918301828280156141b95780601f1061418e576101008083540402835291602001916141b9565b820191906000526020600020905b81548152906001019060200180831161419c57829003601f168201915b5050505050826000616c27565b6000805460408051600160e01b63c214e5e50281526001600160a01b03898116602483015260048201928352604482018b90529092169163c214e5e5918b918b918b918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b15801561424b57600080fd5b505af115801561425f573d6000803e3d6000fd5b505050506040513d602081101561427557600080fd5b505190508015611bf85760068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152611bf8939092909183018282801561430c5780601f106142e15761010080835404028352916020019161430c565b820191906000526020600020905b8154815290600101906020018083116142ef57829003601f168201915b5050505050876001616c27565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561436757600080fd5b505afa15801561437b573d6000803e3d6000fd5b505050506040513d602081101561439157600080fd5b50516001600160a01b031633146143dc57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b80836143e88282614827565b151560011461442b57604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b846144358161690c565b151560011461447c5760408051600160e51b62461bcd02815260206004820152601a6024820152600080516020616fa3833981519152604482015290519081900360640190fd5b6144868787613dd6565b15156001146144df5760408051600160e51b62461bcd02815260206004820152601d60248201527f6f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b6144e98587616dbd565b15156001146145425760408051600160e51b62461bcd02815260206004820152601460248201527f726f6c6520646f6573206e6f7420657869737473000000000000000000000000604482015290519081900360640190fd5b6001546000906001600160a01b031663be322e54878961456181616dd8565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b838110156145b557818101518382015260200161459d565b50505050905090810190601f1680156145e25780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b838110156146155781810151838201526020016145fd565b50505050905090810190601f1680156146425780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b8381101561467557818101518382015260200161465d565b50505050905090810190601f1680156146a25780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b1580156146c357600080fd5b505afa1580156146d7573d6000803e3d6000fd5b505050506040513d60208110156146ed57600080fd5b505160008054604051600160e21b63050e95810281526001600160a01b038c81166004830190815285151560648401526080602484019081528d5160848501528d51969750919093169463143a5604948e948e948e948a9492939092604483019260a401916020890191908190849084905b8381101561477757818101518382015260200161475f565b50505050905090810190601f1680156147a45780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838360005b838110156147d75781810151838201526020016147bf565b50505050905090810190601f1680156148045780820380516001836020036101000a031916815260200191505b509650505050505050600060405180830381600087803b158015612d4057600080fd5b600080546001600160a01b031663e8b42bf4848461484481616dd8565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b031681526020018060200180602001838103835285818151815260200191508051906020019080838360005b838110156148ac578181015183820152602001614894565b50505050905090810190601f1680156148d95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561490c5781810151838201526020016148f4565b50505050905090810190601f1680156149395780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038186803b15801561495957600080fd5b505afa15801561496d573d6000803e3d6000fd5b505050506040513d602081101561498357600080fd5b50511561499257506001613ebe565b6001546000805460408051600160e01b6381d66b230281526001600160a01b03888116600483015291519482169463be322e549493909216926381d66b2392602480840193829003018186803b1580156149eb57600080fd5b505afa1580156149ff573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015614a2857600080fd5b810190808051600160201b811115614a3f57600080fd5b82016020810184811115614a5257600080fd5b8151600160201b811182820187101715614a6b57600080fd5b505092919050505084614a7d86616dd8565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b83811015614ad1578181015183820152602001614ab9565b50505050905090810190601f168015614afe5780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b83811015614b31578181015183820152602001614b19565b50505050905090810190601f168015614b5e5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015614b91578181015183820152602001614b79565b50505050905090810190601f168015614bbe5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b158015613e8f57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015614c2d57600080fd5b505afa158015614c41573d6000803e3d6000fd5b505050506040513d6020811015614c5757600080fd5b50516001600160a01b03163314614ca257604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460009060ff1615614cee5760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b60048054604051600160e01b639e58eb9f028152602481018690526044810185905260609281019283526006805460026000196001831615610100020190911604606483018190526001600160a01b0390931693639e58eb9f9391928892889291829160849091019086908015614da65780601f10614d7b57610100808354040283529160200191614da6565b820191906000526020600020905b815481529060010190602001808311614d8957829003601f168201915b5050945050505050600060405180830381600087803b158015614dc857600080fd5b505af1158015614ddc573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526007805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b71357996509460069490928392918291602481019160c49091019089908015614ea95780601f10614e7e57610100808354040283529160200191614ea9565b820191906000526020600020905b815481529060010190602001808311614e8c57829003601f168201915b5050838103825287546002600019610100600184161502019091160480825260209091019088908015614f1d5780601f10614ef257610100808354040283529160200191614f1d565b820191906000526020600020905b815481529060010190602001808311614f0057829003601f168201915b5050975050505050505050600060405180830381600087803b158015614f4257600080fd5b505af1158015614f56573d6000803e3d6000fd5b505060005460408051600160e01b63cef7f6af028152600481019182526007805460026000196001831615610100020190911604604483018190526001600160a01b03909416955063cef7f6af945092600892918291602482019160640190869080156150045780601f10614fd957610100808354040283529160200191615004565b820191906000526020600020905b815481529060010190602001808311614fe757829003601f168201915b50508381038252845460026000196101006001841615020190911604808252602090910190859080156150785780601f1061504d57610100808354040283529160200191615078565b820191906000526020600020905b81548152906001019060200180831161505b57829003601f168201915b5050945050505050600060405180830381600087803b15801561298f57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156150e857600080fd5b505afa1580156150fc573d6000803e3d6000fd5b505050506040513d602081101561511257600080fd5b50516001600160a01b0316331461515d57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b86868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061519f9250839150616b629050565b15156001146151f05760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8188888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506152349250849150839050614827565b151560011461527757604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b6004805460408051600160e71b623f2a69028152928301908152604483018c90526001600160a01b0390911691631f953480918d918d918d918d919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b15801561531f57600080fd5b505af1158015615333573d6000803e3d6000fd5b5050505060608a8a8a8a60405160200180858580828437600160f91b6017029201918252506001018383808284376040805191909301818103601f1901825290925250955050891593506117f4925050505760035460408051600160e01b633f5e1a4502815260048101918252604481018990526001600160a01b0390921691633f5e1a45918a918a918691819060248101906064018686808284376000838201819052601f909101601f191690920185810384528651815286516020918201939188019250908190849084905b83811015615419578181015183820152602001615401565b50505050905090810190601f1680156154465780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015611a9e57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156154b657600080fd5b505afa1580156154ca573d6000803e3d6000fd5b505050506040513d60208110156154e057600080fd5b50516001600160a01b0316331461552b57604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8061553581615cb0565b151560011461557857604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b82600114806155875750826002145b15156155dd5760408051600160e51b62461bcd02815260206004820152601560248201527f4f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b60008084600114156155f55750600290506003615606565b846002141561560657506003905060055b61564787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508592506169fb915050565b15156001146156a05760408051600160e51b62461bcd02815260206004820152601560248201527f6f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152615739939092909183018282801561572d5780601f106157025761010080835404028352916020019161572d565b820191906000526020600020905b81548152906001019060200180831161571057829003601f168201915b50505050508584616a68565b1561247e576004805460408051600160e01b6314f775f902815260248101899052928301908152604483018990526001600160a01b03909116916314f775f9918a918a918a918190606401858580828437600081840152601f19601f820116905080830192505050945050505050600060405180830381600087803b1580156117e057600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561580f57600080fd5b505afa158015615823573d6000803e3d6000fd5b505050506040513d602081101561583957600080fd5b50516001600160a01b0316331461588457604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8061588e81615cb0565b15156001146158d157604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b600354604051600160e11b63066280a30281526004604482018190526060828201908152606483018790526001600160a01b0390931692630cc5014692889288928c928c92919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b15801561598557600080fd5b505af1158015615999573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d02815260006064820181905260056084830181905260a0600484019081526006805460001960018216156101000201169690960460a485018190526001600160a01b03909516975063e98ac22d96508d948d948d948d9490939092909182916024820191604481019160c4909101908c908015615a6a5780601f10615a3f57610100808354040283529160200191615a6a565b820191906000526020600020905b815481529060010190602001808311615a4d57829003601f168201915b505084810383528981526020018a8a80828437600083820152601f01601f191690910185810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b15801561276f57600080fd5b600a5460068054604080516020601f6002600019600187161561010002019095169490940493840181900481028201810190925282815260609485948594600094919360079360089360ff909116928691830182828015615b7f5780601f10615b5457610100808354040283529160200191615b7f565b820191906000526020600020905b815481529060010190602001808311615b6257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295995088945092508401905082828015615c0d5780601f10615be257610100808354040283529160200191615c0d565b820191906000526020600020905b815481529060010190602001808311615bf057829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815295985087945092508401905082828015615c9b5780601f10615c7057610100808354040283529160200191615c9b565b820191906000526020600020905b815481529060010190602001808311615c7e57829003601f168201915b50505050509150935093509350935090919293565b60408051602080820190815260078054600260001961010060018416150201909116049383018490526000939092829160609091019084908015615d355780601f10615d0a57610100808354040283529160200191615d35565b820191906000526020600020905b815481529060010190602001808311615d1857829003601f168201915b505060408051601f19818403018152828252805160209091012060008054600160e01b6381d66b230285526001600160a01b038a8116600487015293519297509290921694506381d66b239350602480840193829003018186803b158015615d9c57600080fd5b505afa158015615db0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015615dd957600080fd5b810190808051600160201b811115615df057600080fd5b82016020810184811115615e0357600080fd5b8151600160201b811182820187101715615e1c57600080fd5b50509291905050506040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015615e64578181015183820152602001615e4c565b50505050905090810190601f168015615e915780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120149050919050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015615f0257600080fd5b505afa158015615f16573d6000803e3d6000fd5b505050506040513d6020811015615f2c57600080fd5b50516001600160a01b03163314615f7757604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b8086868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250615fbb9250849150839050614827565b1515600114615ffe57604051600160e51b62461bcd0281526004018080602001828103825260228152602001806170566022913960400191505060405180910390fd5b836001148061600d5750836002145b806160185750836003145b151561605857604051600160e51b62461bcd028152600401808060200182810382526025815260200180616fe36025913960400191505060405180910390fd5b600354604051600160e11b63066280a302815260448101869052606060048201908152606482018890526001600160a01b0390921691630cc5014691899189918d918d918b919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015612d4057600080fd5b600254604051600160e21b62539ab302815260206004820190815260248201849052606092839260009283926001600160a01b03169163014e6acc9189918991908190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561618c57600080fd5b505afa1580156161a0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156161c957600080fd5b810190808051600160201b8111156161e057600080fd5b820160208101848111156161f357600080fd5b8151600160201b81118282018710171561620c57600080fd5b50509291906020018051600160201b81111561622757600080fd5b8201602081018481111561623a57600080fd5b8151600160201b81118282018710171561625357600080fd5b50506020820151604090920151949b909a5090985092965091945050505050565b6005546001600160a01b031633146162d65760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600a5460009060ff16156163225760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b61632e60068989616f14565b5061633b60078787616f14565b5061634860088585616f14565b5050600a805460ff1916911515919091179055505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156163b157600080fd5b505afa1580156163c5573d6000803e3d6000fd5b505050506040513d60208110156163db57600080fd5b50516001600160a01b0316331461642657604051600160e51b62461bcd0281526004018080602001828103825260288152602001806170086028913960400191505060405180910390fd5b600a5460019060ff16151581146164755760408051600160e51b62461bcd02815260206004820152601d6024820152600080516020616fc3833981519152604482015290519081900360640190fd5b8161647f81615cb0565b15156001146164c257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806170306026913960400191505060405180910390fd5b60028054604051600160e01b63e98ac22d0281526001600160a01b03878116606483015260016084830181905260a06004840190815260068054600019818516156101000201169690960460a48501819052929094169463e98ac22d9490938e938e938e938e938e9382916024810191604482019160c401908c90801561658a5780601f1061655f5761010080835404028352916020019161658a565b820191906000526020600020905b81548152906001019060200180831161656d57829003601f168201915b505084810383528981526020018a8a80828437600083820152601f01601f191690910185810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b1580156165fd57600080fd5b505af1158015616611573d6000803e3d6000fd5b505060048054604051600160e01b63f9953de50281526020928101928352602481018c90526001600160a01b03909116935063f9953de592508b918b918190604401848480828437600081840152601f19601f8201169050808301925050509350505050600060405180830381600087803b15801561668f57600080fd5b505af11580156166a3573d6000803e3d6000fd5b505060035460408051600160e11b6354bd220302815260048101918252604481018a90526001600160a01b03909216935063a97a44069250899189918d918d919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b15801561675057600080fd5b505af1158015616764573d6000803e3d6000fd5b505050506167a88489898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613dd692505050565b15156001146168015760408051600160e51b62461bcd02815260206004820152601d60248201527f4f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b600054604051600160e01b63e3483a9d0281526001600160a01b0386811660048301908152600160648401819052608060248501908152608485018d9052929094169363e3483a9d9389938e938e93600893909290604481019060a401878780828437600083820152601f01601f1916909101848103835286546002600019610100600184161502019091160480825260209091019150869080156168e75780601f106168bc576101008083540402835291602001916168e7565b820191906000526020600020905b8154815290600101906020018083116168ca57829003601f168201915b5050975050505050505050600060405180830381600087803b158015612d4057600080fd5b6004805460408051600160e01b638c8642df0281526002602482018190529381019182528451604482015284516000946001600160a01b0390941693638c8642df93879391929091829160649091019060208601908083838c5b8381101561697e578181015183820152602001616966565b50505050905090810190601f1680156169ab5780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b1580156169c957600080fd5b505afa1580156169dd573d6000803e3d6000fd5b505050506040513d60208110156169f357600080fd5b505192915050565b6004805460408051600160e01b638c8642df028152602481018590529283019081528451604484015284516000936001600160a01b0390931692638c8642df9287928792829160649091019060208601908083838c83811015613e44578181015183820152602001613e2c565b600254604051600160e21b632c084e190281526001600160a01b03848116602483015260448201849052606060048301908152865160648401528651600094929092169263b02138649288928892889282916084019060208701908083838d5b83811015616ae0578181015183820152602001616ac8565b50505050905090810190601f168015616b0d5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015616b2e57600080fd5b505af1158015616b42573d6000803e3d6000fd5b505050506040513d6020811015616b5857600080fd5b5051949350505050565b600480546040517fffe40d1d00000000000000000000000000000000000000000000000000000000815260209281018381528451602483015284516000946001600160a01b039094169363ffe40d1d9387939283926044909201918501908083838b5b83811015616bdd578181015183820152602001616bc5565b50505050905090810190601f168015616c0a5780820380516001836020036101000a031916815260200191505b509250505060206040518083038186803b1580156169c957600080fd5b8015616d015760025460408051600160e01b635607395b0281526001600160a01b03858116602483015260048201928352865160448301528651931692635607395b9287928792829160640190602086019080838360005b83811015616c97578181015183820152602001616c7f565b50505050905090810190601f168015616cc45780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b158015616ce457600080fd5b505af1158015616cf8573d6000803e3d6000fd5b50505050616db8565b60025460408051600160e11b632ce5eb7f0281526001600160a01b038581166024830152600482019283528651604483015286519316926359cbd6fe9287928792829160640190602086019080838360005b83811015616d6b578181015183820152602001616d53565b50505050905090810190601f168015616d985780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b15801561298f57600080fd5b505050565b6001546000906001600160a01b031663abf5739f8484614a7d815b60048054604051600160e11b630bbe46c502815260209281018381528451602483015284516060946001600160a01b039094169363177c8d8a93879392839260449092019185019080838360005b83811015616e3e578181015183820152602001616e26565b50505050905090810190601f168015616e6b5780820380516001836020036101000a031916815260200191505b509250505060006040518083038186803b158015616e8857600080fd5b505afa158015616e9c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015616ec557600080fd5b810190808051600160201b811115616edc57600080fd5b82016020810184811115616eef57600080fd5b8151600160201b811182820187101715616f0857600080fd5b50909695505050505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10616f555782800160ff19823516178555616f82565b82800160010185558215616f82579182015b82811115616f82578235825591602001919060010190616f67565b50612ebc926131109250905b80821115612ebc5760008155600101616f8e56fe6f7267206e6f7420696e20617070726f76656420737461747573000000000000496e636f7272656374206e6574776f726b20626f6f7420737461747573000000696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f77656463616e2062652063616c6c656420627920696e7465726661636520636f6e7472616374206f6e6c796163636f756e74206973206e6f742061206e6574776f726b2061646d696e206163636f756e746163636f756e74206973206e6f742061206f72672061646d696e206163636f756e74a165627a7a7230582047eb5d5106c9085018e51aa9094a706fff1dfed139d9e1e83d83ee3d32fec65b0029 \ No newline at end of file diff --git a/permission/v1/contract/gen/PermissionsInterface.abi b/permission/v1/contract/gen/PermissionsInterface.abi new file mode 100644 index 0000000000..fc1e434141 --- /dev/null +++ b/permission/v1/contract/gen/PermissionsInterface.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"getPermissionsImpl","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateNodeStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"approveAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminOrg","type":"string"},{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"}],"name":"setPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"}],"name":"assignAccountRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"approveBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"}],"name":"addAdminNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_roleId","type":"string"}],"name":"assignAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_pOrgId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"}],"name":"addSubOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_acct","type":"address"}],"name":"addAdminAccount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_permImplementation","type":"address"}],"name":"setPermImplementation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_access","type":"uint256"},{"name":"_voter","type":"bool"},{"name":"_admin","type":"bool"}],"name":"addNewRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"}],"name":"approveBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"approveOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"validateAccount","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_account","type":"address"}],"name":"approveOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_action","type":"uint256"}],"name":"updateAccountStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"}],"name":"startBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_account","type":"address"}],"name":"addOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"isOrgAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_breadth","type":"uint256"},{"name":"_depth","type":"uint256"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"}],"name":"removeRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"startBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"}],"name":"addNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"isNetworkAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getPendingOp","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permImplUpgradeable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/PermissionsInterface.bin b/permission/v1/contract/gen/PermissionsInterface.bin new file mode 100644 index 0000000000..658054f9f6 --- /dev/null +++ b/permission/v1/contract/gen/PermissionsInterface.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516020806125aa8339810180604052602081101561003057600080fd5b5051600280546001600160a01b0319166001600160a01b0390921691909117905561254a806100606000396000f3fe608060405234801561001057600080fd5b50600436106101cf5760003560e01c80635adbfa7a116101045780639bd38101116100a2578063a97a440611610071578063a97a440614610f50578063bb3b6e801461100e578063d1aa0c201461107c578063f346a3a7146110a2576101cf565b80639bd3810114610d7a578063a5843f0814610df8578063a634301214610e1b578063a97914bf14610ed9576101cf565b80637e461258116100de5780637e46125814610ab157806384b7a84a14610b785780638cb58ef314610bf55780638f362a3e14610cb3576101cf565b80635adbfa7a146109075780635be9672c146109c55780636b568d7614610a33576101cf565b806343de646c116101715780634cff819e1161014b5780634cff819e146106df5780634fe57e7a146107ed578063511bbd9f1461081357806351f604c314610839576101cf565b806343de646c146105f057806344478e79146106bb5780634cbfa82e146106d7576101cf565b80631b610220116101ad5780631b6102201461032f5780632f7f0a121461043d5780633e239b231461050b5780633f25c28814610582576101cf565b806303ed6933146101d45780630cc50146146101f857806316724c44146102b8575b600080fd5b6101dc61120e565b604080516001600160a01b039092168252519081900360200190f35b6102b66004803603606081101561020e57600080fd5b810190602081018135600160201b81111561022857600080fd5b82018360208201111561023a57600080fd5b803590602001918460018302840111600160201b8311171561025b57600080fd5b919390929091602081019035600160201b81111561027857600080fd5b82018360208201111561028a57600080fd5b803590602001918460018302840111600160201b831117156102ab57600080fd5b91935091503561121d565b005b6102b6600480360360408110156102ce57600080fd5b810190602081018135600160201b8111156102e857600080fd5b8201836020820111156102fa57600080fd5b803590602001918460018302840111600160201b8311171561031b57600080fd5b9193509150356001600160a01b03166112fa565b6102b66004803603606081101561034557600080fd5b810190602081018135600160201b81111561035f57600080fd5b82018360208201111561037157600080fd5b803590602001918460018302840111600160201b8311171561039257600080fd5b919390929091602081019035600160201b8111156103af57600080fd5b8201836020820111156103c157600080fd5b803590602001918460018302840111600160201b831117156103e257600080fd5b919390929091602081019035600160201b8111156103ff57600080fd5b82018360208201111561041157600080fd5b803590602001918460018302840111600160201b8311171561043257600080fd5b5090925090506113a9565b6102b66004803603606081101561045357600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561047d57600080fd5b82018360208201111561048f57600080fd5b803590602001918460018302840111600160201b831117156104b057600080fd5b919390929091602081019035600160201b8111156104cd57600080fd5b8201836020820111156104df57600080fd5b803590602001918460018302840111600160201b8311171561050057600080fd5b5090925090506114a0565b6102b66004803603604081101561052157600080fd5b810190602081018135600160201b81111561053b57600080fd5b82018360208201111561054d57600080fd5b803590602001918460018302840111600160201b8311171561056e57600080fd5b9193509150356001600160a01b0316611562565b6102b66004803603602081101561059857600080fd5b810190602081018135600160201b8111156105b257600080fd5b8201836020820111156105c457600080fd5b803590602001918460018302840111600160201b831117156105e557600080fd5b5090925090506115f4565b6102b66004803603606081101561060657600080fd5b810190602081018135600160201b81111561062057600080fd5b82018360208201111561063257600080fd5b803590602001918460018302840111600160201b8311171561065357600080fd5b919390926001600160a01b0383351692604081019060200135600160201b81111561067d57600080fd5b82018360208201111561068f57600080fd5b803590602001918460018302840111600160201b831117156106b057600080fd5b50909250905061168a565b6106c3611749565b604080519115158252519081900360200190f35b6106c36117cb565b6102b6600480360360608110156106f557600080fd5b810190602081018135600160201b81111561070f57600080fd5b82018360208201111561072157600080fd5b803590602001918460018302840111600160201b8311171561074257600080fd5b919390929091602081019035600160201b81111561075f57600080fd5b82018360208201111561077157600080fd5b803590602001918460018302840111600160201b8311171561079257600080fd5b919390929091602081019035600160201b8111156107af57600080fd5b8201836020820111156107c157600080fd5b803590602001918460018302840111600160201b831117156107e257600080fd5b50909250905061182e565b6102b66004803603602081101561080357600080fd5b50356001600160a01b031661190f565b6102b66004803603602081101561082957600080fd5b50356001600160a01b0316611978565b6102b6600480360360a081101561084f57600080fd5b810190602081018135600160201b81111561086957600080fd5b82018360208201111561087b57600080fd5b803590602001918460018302840111600160201b8311171561089c57600080fd5b919390929091602081019035600160201b8111156108b957600080fd5b8201836020820111156108cb57600080fd5b803590602001918460018302840111600160201b831117156108ec57600080fd5b919350915080359060208101351515906040013515156119fc565b6102b66004803603604081101561091d57600080fd5b810190602081018135600160201b81111561093757600080fd5b82018360208201111561094957600080fd5b803590602001918460018302840111600160201b8311171561096a57600080fd5b919390929091602081019035600160201b81111561098757600080fd5b82018360208201111561099957600080fd5b803590602001918460018302840111600160201b831117156109ba57600080fd5b509092509050611af1565b6102b6600480360360408110156109db57600080fd5b810190602081018135600160201b8111156109f557600080fd5b820183602082011115610a0757600080fd5b803590602001918460018302840111600160201b83111715610a2857600080fd5b919350915035611bc3565b6106c360048036036040811015610a4957600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610a7357600080fd5b820183602082011115610a8557600080fd5b803590602001918460018302840111600160201b83111715610aa657600080fd5b509092509050611c50565b6102b660048036036060811015610ac757600080fd5b810190602081018135600160201b811115610ae157600080fd5b820183602082011115610af357600080fd5b803590602001918460018302840111600160201b83111715610b1457600080fd5b919390929091602081019035600160201b811115610b3157600080fd5b820183602082011115610b4357600080fd5b803590602001918460018302840111600160201b83111715610b6457600080fd5b9193509150356001600160a01b0316611d08565b6102b660048036036060811015610b8e57600080fd5b810190602081018135600160201b811115610ba857600080fd5b820183602082011115610bba57600080fd5b803590602001918460018302840111600160201b83111715610bdb57600080fd5b91935091506001600160a01b038135169060200135611dc7565b6102b660048036036040811015610c0b57600080fd5b810190602081018135600160201b811115610c2557600080fd5b820183602082011115610c3757600080fd5b803590602001918460018302840111600160201b83111715610c5857600080fd5b919390929091602081019035600160201b811115610c7557600080fd5b820183602082011115610c8757600080fd5b803590602001918460018302840111600160201b83111715610ca857600080fd5b509092509050611e61565b6102b660048036036060811015610cc957600080fd5b810190602081018135600160201b811115610ce357600080fd5b820183602082011115610cf557600080fd5b803590602001918460018302840111600160201b83111715610d1657600080fd5b919390929091602081019035600160201b811115610d3357600080fd5b820183602082011115610d4557600080fd5b803590602001918460018302840111600160201b83111715610d6657600080fd5b9193509150356001600160a01b0316611f15565b6106c360048036036040811015610d9057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610dba57600080fd5b820183602082011115610dcc57600080fd5b803590602001918460018302840111600160201b83111715610ded57600080fd5b509092509050611fd4565b6102b660048036036040811015610e0e57600080fd5b5080359060200135612058565b6102b660048036036040811015610e3157600080fd5b810190602081018135600160201b811115610e4b57600080fd5b820183602082011115610e5d57600080fd5b803590602001918460018302840111600160201b83111715610e7e57600080fd5b919390929091602081019035600160201b811115610e9b57600080fd5b820183602082011115610ead57600080fd5b803590602001918460018302840111600160201b83111715610ece57600080fd5b5090925090506120aa565b6102b660048036036040811015610eef57600080fd5b810190602081018135600160201b811115610f0957600080fd5b820183602082011115610f1b57600080fd5b803590602001918460018302840111600160201b83111715610f3c57600080fd5b9193509150356001600160a01b031661215e565b6102b660048036036040811015610f6657600080fd5b810190602081018135600160201b811115610f8057600080fd5b820183602082011115610f9257600080fd5b803590602001918460018302840111600160201b83111715610fb357600080fd5b919390929091602081019035600160201b811115610fd057600080fd5b820183602082011115610fe257600080fd5b803590602001918460018302840111600160201b8311171561100357600080fd5b5090925090506121f0565b6102b66004803603604081101561102457600080fd5b810190602081018135600160201b81111561103e57600080fd5b82018360208201111561105057600080fd5b803590602001918460018302840111600160201b8311171561107157600080fd5b9193509150356122a4565b6106c36004803603602081101561109257600080fd5b50356001600160a01b0316612331565b611110600480360360208110156110b857600080fd5b810190602081018135600160201b8111156110d257600080fd5b8201836020820111156110e457600080fd5b803590602001918460018302840111600160201b8311171561110557600080fd5b5090925090506123b4565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b8381101561116f578181015183820152602001611157565b50505050905090810190601f16801561119c5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b838110156111cf5781810151838201526020016111b7565b50505050905090810190601f1680156111fc5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b6000546001600160a01b031690565b600054604051600160e01b63dbfad711028152604481018390523360648201819052608060048301908152608483018890526001600160a01b039093169263dbfad711928992899289928992899290918190602481019060a401898980828437600083820152601f01601f191690910184810383528781526020019050878780828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b505af11580156112ef573d6000803e3d6000fd5b505050505050505050565b600054604051600160e01b63888430410281526001600160a01b03838116602483015233604483018190526060600484019081526064840187905291909316926388843041928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b505af11580156113a0573d6000803e3d6000fd5b50505050505050565b600054604051600160e51b62db0811028152606060048201908152606482018890526001600160a01b0390921691631b610220918991899189918991899189918190602481019060448101906084018a8a80828437600083820152601f01601f191690910185810384528881526020019050888880828437600083820152601f01601f191690910185810383528681526020019050868680828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b15801561148057600080fd5b505af1158015611494573d6000803e3d6000fd5b50505050505050505050565b600054604051600160e01b638baa81910281526001600160a01b03878116600483019081523360648401819052608060248501908152608485018990529290941693638baa8191938a938a938a938a938a9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b600054604051600160e01b634b20f45f0281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692634b20f45f928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b600054604051600160e31b6307e4b851028152602060048201908152602482018490526001600160a01b0390921691633f25c28891859185918190604401848480828437600081840152601f19601f8201169050808301925050509350505050600060405180830381600087803b15801561166e57600080fd5b505af1158015611682573d6000803e3d6000fd5b505050505050565b600054604051600160e01b63404bf3eb0281526001600160a01b038581166024830152336064830181905260806004840190815260848401899052919093169263404bf3eb9289928992899289928992918190604481019060a401898980828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b03166344478e796040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561179a57600080fd5b505af11580156117ae573d6000803e3d6000fd5b505050506040513d60208110156117c457600080fd5b5051905090565b60008060009054906101000a90046001600160a01b03166001600160a01b0316634cbfa82e6040518163ffffffff1660e01b815260040160206040518083038186803b15801561181a57600080fd5b505afa1580156117ae573d6000803e3d6000fd5b600054604051600160e51b63053269430281523360648201819052608060048301908152608483018990526001600160a01b039093169263a64d2860928a928a928a928a928a928a9281906024810190604481019060a4018b8b80828437600083820152601f01601f191690910185810384528981526020019050898980828437600083820152601f01601f191690910185810383528781526020019050878780828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b15801561148057600080fd5b6000805460408051600160e11b6327f2bf3d0281526001600160a01b03858116600483015291519190921692634fe57e7a926024808201939182900301818387803b15801561195d57600080fd5b505af1158015611971573d6000803e3d6000fd5b5050505050565b6002546001600160a01b031633146119da5760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b600054604051600160e11b630d82613b02815260448101859052831515606482015282151560848201523360a4820181905260c06004830190815260c483018a90526001600160a01b0390931692631b04c276928b928b928b928b928b928b928b9291908190602481019060e4018b8b80828437600083820152601f01601f191690910184810383528981526020019050898980828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b158015611ad057600080fd5b505af1158015611ae4573d6000803e3d6000fd5b5050505050505050505050565b600054604051600160e01b63655a8ef50281523360448201819052606060048301908152606483018790526001600160a01b039093169263655a8ef5928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b505af1158015611bb9573d6000803e3d6000fd5b5050505050505050565b600054604051600160e21b632d551959028152602481018390523360448201819052606060048301908152606483018690526001600160a01b039093169263b5546564928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b6000805460408051600160e11b6335ab46bb0281526001600160a01b03878116600483019081526024830193845260448301879052931692636b568d76928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015611cd457600080fd5b505afa158015611ce8573d6000803e3d6000fd5b505050506040513d6020811015611cfe57600080fd5b5051949350505050565b600054604051600160e11b631de03ef50281526001600160a01b0383811660448301523360648301819052608060048401908152608484018990529190931692633bc07dea9289928992899289928992918190602481019060a401898980828437600083820152601f01601f191690910184810383528781526020019050878780828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b600054604051600160e11b6302740f8f0281526001600160a01b0384811660248301526044820184905233606483018190526080600484019081526084840188905291909316926304e81f1e92889288928892889290819060a401878780828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e01b63c3dc8e090281523360448201819052606060048301908152606483018790526001600160a01b039093169263c3dc8e09928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e11b637c917c010281526001600160a01b038381166044830152336064830181905260806004840190815260848401899052919093169263f922f8029289928992899289928992918190602481019060a401898980828437600083820152601f01601f191690910184810383528781526020019050878780828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b1580156112db57600080fd5b6000805460408051600160e01b639bd381010281526001600160a01b03878116600483019081526024830193845260448301879052931692639bd38101928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015611cd457600080fd5b6000805460408051600160e31b6314b087e1028152600481018690526024810185905290516001600160a01b039092169263a5843f089260448084019382900301818387803b15801561166e57600080fd5b600054604051600160e11b632e52d6df0281523360448201819052606060048301908152606483018790526001600160a01b0390931692635ca5adbe928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e11b630e124c890281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692631c249912928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b600054604051600160e01b6359a260a30281523360448201819052606060048301908152606483018790526001600160a01b03909316926359a260a3928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015611ba557600080fd5b600054604051600160e01b633cf5f33b028152602481018390523360448201819052606060048301908152606483018690526001600160a01b0390931692633cf5f33b928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561138c57600080fd5b6000805460408051600160e51b63068d50610281526001600160a01b0385811660048301529151919092169163d1aa0c20916024808301926020929190829003018186803b15801561238257600080fd5b505afa158015612396573d6000803e3d6000fd5b505050506040513d60208110156123ac57600080fd5b505192915050565b60008054604051600160e01b63f346a3a7028152602060048201908152602482018590526060938493909283926001600160a01b039092169163f346a3a791899189918190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561243657600080fd5b505afa15801561244a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052608081101561247357600080fd5b810190808051600160201b81111561248a57600080fd5b8201602081018481111561249d57600080fd5b8151600160201b8111828201871017156124b657600080fd5b50509291906020018051600160201b8111156124d157600080fd5b820160208101848111156124e457600080fd5b8151600160201b8111828201871017156124fd57600080fd5b50506020820151604090920151949b909a509098509296509194505050505056fea165627a7a72305820c59bf7b1eb3a15d1406b140bc566b70353e3ef021637abb4ecb03c63261f92b10029 \ No newline at end of file diff --git a/permission/v1/contract/gen/PermissionsUpgradable.abi b/permission/v1/contract/gen/PermissionsUpgradable.abi new file mode 100644 index 0000000000..035f83a651 --- /dev/null +++ b/permission/v1/contract/gen/PermissionsUpgradable.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"getPermImpl","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_proposedImpl","type":"address"}],"name":"confirmImplChange","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getGuardian","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getPermInterface","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_permInterface","type":"address"},{"name":"_permImpl","type":"address"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_guardian","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/PermissionsUpgradable.bin b/permission/v1/contract/gen/PermissionsUpgradable.bin new file mode 100644 index 0000000000..463c35008c --- /dev/null +++ b/permission/v1/contract/gen/PermissionsUpgradable.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516020806106e78339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905560028054600160a01b60ff0219169055610675806100726000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e32cf901461005c57806322bcb39a14610080578063a75b87d2146100a8578063e572515c146100b0578063f09a4016146100b8575b600080fd5b6100646100e6565b604080516001600160a01b039092168252519081900360200190f35b6100a66004803603602081101561009657600080fd5b50356001600160a01b03166100f5565b005b61006461030b565b61006461031a565b6100a6600480360360408110156100ce57600080fd5b506001600160a01b0381358116916020013516610329565b6001546001600160a01b031690565b6000546001600160a01b0316331461014b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60608060606000600160009054906101000a90046001600160a01b03166001600160a01b031663cc9ba6fa6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a057600080fd5b505afa1580156101b4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156101dd57600080fd5b8101908080516401000000008111156101f557600080fd5b8201602081018481111561020857600080fd5b815164010000000081118282018710171561022257600080fd5b5050929190602001805164010000000081111561023e57600080fd5b8201602081018481111561025157600080fd5b815164010000000081118282018710171561026b57600080fd5b5050929190602001805164010000000081111561028757600080fd5b8201602081018481111561029a57600080fd5b81516401000000008111828201871017156102b457600080fd5b50506020909101519498509296509194509192506102d9915086905085858585610443565b600180546001600160a01b0319166001600160a01b03878116919091179182905561030491166105e4565b5050505050565b6000546001600160a01b031690565b6002546001600160a01b031690565b6000546001600160a01b0316331461037f5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600254600160a01b900460ff16156103e15760408051600160e51b62461bcd02815260206004820152601960248201527f63616e206265206578656375746564206f6e6c79206f6e636500000000000000604482015290519081900360640190fd5b600180546001600160a01b038084166001600160a01b031992831617928390556002805486831693169290921790915561041b91166105e4565b50506002805474ff00000000000000000000000000000000000000001916600160a01b179055565b846001600160a01b031663f5ad584a858585856040518563ffffffff1660e01b81526004018080602001806020018060200185151515158152602001848103845288818151815260200191508051906020019080838360005b838110156104b457818101518382015260200161049c565b50505050905090810190601f1680156104e15780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156105145781810151838201526020016104fc565b50505050905090810190601f1680156105415780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b8381101561057457818101518382015260200161055c565b50505050905090810190601f1680156105a15780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156105c557600080fd5b505af11580156105d9573d6000803e3d6000fd5b505050505050505050565b60025460408051600160e01b63511bbd9f0281526001600160a01b0384811660048301529151919092169163511bbd9f91602480830192600092919082900301818387803b15801561063557600080fd5b505af1158015610304573d6000803e3d6000fdfea165627a7a72305820baed98682426c4ca5713954d4aec5ce8f78637bbf627bd8f53ff37aac2394a950029 \ No newline at end of file diff --git a/permission/v1/contract/gen/RoleManager.abi b/permission/v1/contract/gen/RoleManager.abi new file mode 100644 index 0000000000..28e7e76ae3 --- /dev/null +++ b/permission/v1/contract/gen/RoleManager.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"}],"name":"getRoleDetails","outputs":[{"name":"roleId","type":"string"},{"name":"orgId","type":"string"},{"name":"accessType","type":"uint256"},{"name":"voter","type":"bool"},{"name":"admin","type":"bool"},{"name":"active","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_baseAccess","type":"uint256"},{"name":"_isVoter","type":"bool"},{"name":"_isAdmin","type":"bool"}],"name":"addRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfRoles","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_rIndex","type":"uint256"}],"name":"getRoleDetailsFromIndex","outputs":[{"name":"roleId","type":"string"},{"name":"orgId","type":"string"},{"name":"accessType","type":"uint256"},{"name":"voter","type":"bool"},{"name":"admin","type":"bool"},{"name":"active","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"}],"name":"removeRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"roleExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"isAdminRole","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"isVoterRole","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_baseAccess","type":"uint256"},{"indexed":false,"name":"_isVoter","type":"bool"},{"indexed":false,"name":"_isAdmin","type":"bool"}],"name":"RoleCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"RoleRevoked","type":"event"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/RoleManager.bin b/permission/v1/contract/gen/RoleManager.bin new file mode 100644 index 0000000000..84173ffc18 --- /dev/null +++ b/permission/v1/contract/gen/RoleManager.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516020806122418339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556121df806100626000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063a63430121161005b578063a6343012146103ba578063abf5739f14610478578063be322e541461063a578063deb16ba71461074857610088565b80631870aba31461008d5780637b7135791461024957806387f55d3114610383578063a451d4a81461039d575b600080fd5b61014b600480360360408110156100a357600080fd5b810190602081018135600160201b8111156100bd57600080fd5b8201836020820111156100cf57600080fd5b803590602001918460018302840111600160201b831117156100f057600080fd5b919390929091602081019035600160201b81111561010d57600080fd5b82018360208201111561011f57600080fd5b803590602001918460018302840111600160201b8311171561014057600080fd5b509092509050610856565b604080519081018590528315156060820152821515608082015281151560a082015260c08082528751908201528651819060208083019160e08401918b019080838360005b838110156101a8578181015183820152602001610190565b50505050905090810190601f1680156101d55780820380516001836020036101000a031916815260200191505b5083810382528851815288516020918201918a019080838360005b838110156102085781810151838201526020016101f0565b50505050905090810190601f1680156102355780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610381600480360360a081101561025f57600080fd5b810190602081018135600160201b81111561027957600080fd5b82018360208201111561028b57600080fd5b803590602001918460018302840111600160201b831117156102ac57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460018302840111600160201b8311171561033157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550508235935050506020810135151590604001351515610bdc565b005b61038b611182565b60408051918252519081900360200190f35b61014b600480360360208110156103b357600080fd5b5035611189565b610381600480360360408110156103d057600080fd5b810190602081018135600160201b8111156103ea57600080fd5b8201836020820111156103fc57600080fd5b803590602001918460018302840111600160201b8311171561041d57600080fd5b919390929091602081019035600160201b81111561043a57600080fd5b82018360208201111561044c57600080fd5b803590602001918460018302840111600160201b8311171561046d57600080fd5b5090925090506113a7565b6106266004803603606081101561048e57600080fd5b810190602081018135600160201b8111156104a857600080fd5b8201836020820111156104ba57600080fd5b803590602001918460018302840111600160201b831117156104db57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561052d57600080fd5b82018360208201111561053f57600080fd5b803590602001918460018302840111600160201b8311171561056057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156105b257600080fd5b8201836020820111156105c457600080fd5b803590602001918460018302840111600160201b831117156105e557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506116a2945050505050565b604080519115158252519081900360200190f35b6106266004803603606081101561065057600080fd5b810190602081018135600160201b81111561066a57600080fd5b82018360208201111561067c57600080fd5b803590602001918460018302840111600160201b8311171561069d57600080fd5b919390929091602081019035600160201b8111156106ba57600080fd5b8201836020820111156106cc57600080fd5b803590602001918460018302840111600160201b831117156106ed57600080fd5b919390929091602081019035600160201b81111561070a57600080fd5b82018360208201111561071c57600080fd5b803590602001918460018302840111600160201b8311171561073d57600080fd5b509092509050611916565b6106266004803603606081101561075e57600080fd5b810190602081018135600160201b81111561077857600080fd5b82018360208201111561078a57600080fd5b803590602001918460018302840111600160201b831117156107ab57600080fd5b919390929091602081019035600160201b8111156107c857600080fd5b8201836020820111156107da57600080fd5b803590602001918460018302840111600160201b831117156107fb57600080fd5b919390929091602081019035600160201b81111561081857600080fd5b82018360208201111561082a57600080fd5b803590602001918460018302840111600160201b8311171561084b57600080fd5b509092509050611c96565b6060806000806000806108e08a8a8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8e018190048102820181019092528c815292508c91508b90819084018382808284376000920182905250604080516020810190915290815292506116a2915050565b151561094a57898960008060008085858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506040805160208101909152908152939f50929d50959b509399509197509550610bcf945050505050565b60006109bf8b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8f018190048102820181019092528d815292508d91508c908190840183828082843760009201919091525061200b92505050565b90506001818154811015156109d057fe5b90600052602060002090600402016000016001828154811015156109f057fe5b9060005260206000209060040201600101600183815481101515610a1057fe5b906000526020600020906004020160020154600184815481101515610a3157fe5b60009182526020909120600360049092020101546001805460ff9092169186908110610a5957fe5b906000526020600020906004020160030160019054906101000a900460ff16600186815481101515610a8757fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff169290918891830182828015610b2c5780601f10610b0157610100808354040283529160200191610b2c565b820191906000526020600020905b815481529060010190602001808311610b0f57829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a945092508401905082828015610bba5780601f10610b8f57610100808354040283529160200191610bba565b820191906000526020600020905b815481529060010190602001808311610b9d57829003601f168201915b50505050509450965096509650965096509650505b9499939850945094509450565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610c2957600080fd5b505afa158015610c3d573d6000803e3d6000fd5b505050506040513d6020811015610c5357600080fd5b50516001600160a01b03163314610ca85760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60048310610d005760408051600160e51b62461bcd02815260206004820152601460248201527f696e76616c6964206163636573732076616c7565000000000000000000000000604482015290519081900360640190fd5b600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015610d4a578181015183820152602001610d32565b50505050905090810190601f168015610d775780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015610daa578181015183820152602001610d92565b50505050905090810190601f168015610dd75780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515610e5c5760408051600160e51b62461bcd02815260206004820152601760248201527f726f6c652065786973747320666f7220746865206f7267000000000000000000604482015290519081900360640190fd5b60038054600101908190556040805160208082018381528951606084015289516002946000948c948c94938493830192608001918701908083838b5b83811015610eb0578181015183820152602001610e98565b50505050905090810190601f168015610edd5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015610f10578181015183820152602001610ef8565b50505050905090810190601f168015610f3d5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208852878201989098529587016000908120989098555050845160c0810186528b81528085018b905294850189905250505084151560608301528315156080830152600160a083018190528054808201808355919094528251805191946004027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60192610fe79284929091019061211b565b506020828101518051611000926001850192019061211b565b5060408281015160028301556060808401516003909301805460808087015160a09788015160ff199093169615159690961761ff001916610100961515969096029590951762ff0000191662010000911515919091021790558151918201889052861515908201528415159181019190915281815287519181019190915286517fefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c92508791879187918791879190819060208083019160c08401918a019080838360005b838110156110dc5781810151838201526020016110c4565b50505050905090810190601f1680156111095780820380516001836020036101000a031916815260200191505b50838103825287518152875160209182019189019080838360005b8381101561113c578181015183820152602001611124565b50505050905090810190601f1680156111695780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15050505050565b6001545b90565b6060806000806000806001878154811015156111a157fe5b90600052602060002090600402016000016001888154811015156111c157fe5b90600052602060002090600402016001016001898154811015156111e157fe5b90600052602060002090600402016002015460018a81548110151561120257fe5b60009182526020909120600360049092020101546001805460ff909216918c90811061122a57fe5b906000526020600020906004020160030160019054906101000a900460ff1660018c81548110151561125857fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff1692909188918301828280156112fd5780601f106112d2576101008083540402835291602001916112fd565b820191906000526020600020905b8154815290600101906020018083116112e057829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a94509250840190508282801561138b5780601f106113605761010080835404028352916020019161138b565b820191906000526020600020905b81548152906001019060200180831161136e57829003601f168201915b5050505050945095509550955095509550955091939550919395565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156113f457600080fd5b505afa158015611408573d6000803e3d6000fd5b505050506040513d602081101561141e57600080fd5b50516001600160a01b031633146114735760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000858585856040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f82011690508083019250505096505050505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415151561155f5760408051600160e51b62461bcd02815260206004820152601360248201527f726f6c6520646f6573206e6f7420657869737400000000000000000000000000604482015290519081900360640190fd5b60006115d485858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525061200b92505050565b905060006001828154811015156115e757fe5b906000526020600020906004020160030160026101000a81548160ff0219169083151502179055507f1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b600080600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156116ef5781810151838201526020016116d7565b50505050905090810190601f16801561171c5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561174f578181015183820152602001611737565b50505050905090810190601f16801561177c5780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415156117f3576117bb858561200b565b90506001818154811015156117cc57fe5b906000526020600020906004020160030160029054906101000a900460ff1691505061190f565b600260008685604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561183d578181015183820152602001611825565b50505050905090810190601f16801561186a5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561189d578181015183820152602001611885565b50505050905090810190601f1680156118ca5780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611909576117bb858461200b565b60009150505b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561196557600080fd5b505afa158015611979573d6000803e3d6000fd5b505050506040513d602081101561198f57600080fd5b50516001600160a01b031633146119e45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611a8b87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506116a292505050565b1515611a9957506000611c8c565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611bb057611ba988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a915089908190840183828082843760009201919091525061200b92505050565b9050611c26565b611c2388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061200b92505050565b90505b6001805482908110611c3457fe5b906000526020600020906004020160030160029054906101000a900460ff168015611c8857506001805482908110611c6857fe5b906000526020600020906004020160030160019054906101000a900460ff165b9150505b9695505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611ce557600080fd5b505afa158015611cf9573d6000803e3d6000fd5b505050506040513d6020811015611d0f57600080fd5b50516001600160a01b03163314611d645760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611e0b87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506116a292505050565b1515611e1957506000611c8c565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611f3057611f2988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a915089908190840183828082843760009201919091525061200b92505050565b9050611fa6565b611fa388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061200b92505050565b90505b6001805482908110611fb457fe5b906000526020600020906004020160030160029054906101000a900460ff168015611c8857506001805482908110611fe857fe5b600091825260209091206004909102016003015460ff1698975050505050505050565b60006001600260008585604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015612059578181015183820152602001612041565b50505050905090810190601f1680156120865780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156120b95781810151838201526020016120a1565b50505050905090810190601f1680156120e65780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205403905092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061215c57805160ff1916838001178555612189565b82800160010185558215612189579182015b8281111561218957825182559160200191906001019061216e565b50612195929150612199565b5090565b61118691905b80821115612195576000815560010161219f56fea165627a7a723058209059a9af47943da0750b529cb5cf17b9f0745cfb3bea00dad68345c815bbec800029 \ No newline at end of file diff --git a/permission/v1/contract/gen/VoterManager.abi b/permission/v1/contract/gen/VoterManager.abi new file mode 100644 index 0000000000..10c875209b --- /dev/null +++ b/permission/v1/contract/gen/VoterManager.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getPendingOpDetails","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_vAccount","type":"address"}],"name":"addVoter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_vAccount","type":"address"}],"name":"deleteVoter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_authOrg","type":"string"},{"name":"_vAccount","type":"address"},{"name":"_pendingOp","type":"uint256"}],"name":"processVote","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_authOrg","type":"string"},{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_account","type":"address"},{"name":"_pendingOp","type":"uint256"}],"name":"addVotingItem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_vAccount","type":"address"}],"name":"VoterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_vAccount","type":"address"}],"name":"VoterDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"}],"name":"VotingItemAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"}],"name":"VoteProcessed","type":"event"}] \ No newline at end of file diff --git a/permission/v1/contract/gen/VoterManager.bin b/permission/v1/contract/gen/VoterManager.bin new file mode 100644 index 0000000000..9698f14afd --- /dev/null +++ b/permission/v1/contract/gen/VoterManager.bin @@ -0,0 +1 @@ +6080604052600060035534801561001557600080fd5b50604051602080611fe48339810180604052602081101561003557600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055611f7d806100676000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063014e6acc1461005c5780635607395b146101c857806359cbd6fe14610241578063b0213864146102b8578063e98ac22d14610349575b600080fd5b6100ca6004803603602081101561007257600080fd5b810190602081018135600160201b81111561008c57600080fd5b82018360208201111561009e57600080fd5b803590602001918460018302840111600160201b831117156100bf57600080fd5b509092509050610466565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015610129578181015183820152602001610111565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015610189578181015183820152602001610171565b50505050905090810190601f1680156101b65780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61023f600480360360408110156101de57600080fd5b810190602081018135600160201b8111156101f857600080fd5b82018360208201111561020a57600080fd5b803590602001918460018302840111600160201b8311171561022b57600080fd5b9193509150356001600160a01b0316610740565b005b61023f6004803603604081101561025757600080fd5b810190602081018135600160201b81111561027157600080fd5b82018360208201111561028357600080fd5b803590602001918460018302840111600160201b831117156102a457600080fd5b9193509150356001600160a01b0316610f06565b610335600480360360608110156102ce57600080fd5b810190602081018135600160201b8111156102e857600080fd5b8201836020820111156102fa57600080fd5b803590602001918460018302840111600160201b8311171561031b57600080fd5b91935091506001600160a01b0381351690602001356111e8565b604080519115158252519081900360200190f35b61023f600480360360a081101561035f57600080fd5b810190602081018135600160201b81111561037957600080fd5b82018360208201111561038b57600080fd5b803590602001918460018302840111600160201b831117156103ac57600080fd5b919390929091602081019035600160201b8111156103c957600080fd5b8201836020820111156103db57600080fd5b803590602001918460018302840111600160201b831117156103fc57600080fd5b919390929091602081019035600160201b81111561041957600080fd5b82018360208201111561042b57600080fd5b803590602001918460018302840111600160201b8311171561044c57600080fd5b91935091506001600160a01b0381351690602001356116f8565b6060806000806000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156104b957600080fd5b505afa1580156104cd573d6000803e3d6000fd5b505050506040513d60208110156104e357600080fd5b50516001600160a01b031633146105385760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600061057987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561058a57fe5b90600052602060002090600b02016004016000016001828154811015156105ad57fe5b90600052602060002090600b02016004016001016001838154811015156105d057fe5b600091825260209091206006600b909202010154600180546001600160a01b0390921691859081106105fe57fe5b60009182526020918290206007600b909202010154845460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928691908301828280156106995780601f1061066e57610100808354040283529160200191610699565b820191906000526020600020905b81548152906001019060200180831161067c57829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156107275780601f106106fc57610100808354040283529160200191610727565b820191906000526020600020905b81548152906001019060200180831161070a57829003601f168201915b5050505050925094509450945094505092959194509250565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561078d57600080fd5b505afa1580156107a1573d6000803e3d6000fd5b505050506040513d60208110156107b757600080fd5b50516001600160a01b0316331461080c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000848460405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415610b78576003805460010190819055604080516020808201908152918101859052600291600091879187918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506000600180548091906001016109069190611cdd565b9050838360018381548110151561091957fe5b6000918252602090912061093393600b9092020191611d0e565b506001808281548110151561094457fe5b90600052602060002090600b0201600101819055506001808281548110151561096957fe5b90600052602060002090600b020160020181905550600060018281548110151561098f57fe5b90600052602060002090600b020160030181905550604051806020016040528060008152506001828154811015156109c357fe5b90600052602060002090600b020160040160000190805190602001906109ea929190611d8c565b506040805160208101909152600081526001805483908110610a0857fe5b90600052602060002090600b02016004016001019080519060200190610a2f929190611d8c565b506000600182815481101515610a4157fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b0393909316929092179091556001805483908110610a8157fe5b600091825260209091206007600b9092020101556001805482908110610aa357fe5b90600052602060002090600b020160010154600182815481101515610ac457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610afb57fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b969096029093016008018054938401815586529290942093519301805492516001600160a01b03199093169390911692909217600160a01b60ff021916600160a01b9115159190910217905550610e90565b6000610bb984848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050600181815481101515610bca57fe5b600091825260208083206001600160a01b03861684526009600b9093020191909101905260409020541515610d2c576001805482908110610c0757fe5b600091825260209091206001600b909202018101805482019055805482908110610c2d57fe5b90600052602060002090600b020160010154600182815481101515610c4e57fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610c8557fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b96909602909301600801805480850182559087529390952090519201805493516001600160a01b03199094169290941691909117600160a01b60ff021916600160a01b9215159290920291909117909155805482908110610d0d57fe5b600091825260209091206002600b909202010180546001019055610e8e565b6000610d6f85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250879250611ba5915050565b9050600182815481101515610d8057fe5b90600052602060002090600b020160080181815481101515610d9e57fe5b600091825260209091200154600160a01b900460ff16151560011415610e0e5760408051600160e51b62461bcd02815260206004820152600f60248201527f616c7265616479206120766f7465720000000000000000000000000000000000604482015290519081900360640190fd5b60018083815481101515610e1e57fe5b90600052602060002090600b020160080182815481101515610e3c57fe5b60009182526020909120018054911515600160a01b02600160a01b60ff02199092169190911790556001805483908110610e7257fe5b600091825260209091206002600b909202010180546001019055505b505b604080516001600160a01b03831660208201528181529081018390527f424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574908490849084908060608101858580828437600083820152604051601f909101601f1916909201829003965090945050505050a1505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610f5357600080fd5b505afa158015610f67573d6000803e3d6000fd5b505050506040513d6020811015610f7d57600080fd5b50516001600160a01b03163314610fd25760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250849250611016915083905082611bf7565b15156001146110645760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b60006110a586868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060006110ea87878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250899250611ba5915050565b90506001828154811015156110fb57fe5b6000918252602082206002600b90920201018054600019019055600180548490811061112357fe5b90600052602060002090600b02016008018281548110151561114157fe5b9060005260206000200160000160146101000a81548160ff0219169083151502179055507f654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b68787876040518080602001836001600160a01b03166001600160a01b031681526020018281038252858582818152602001925080828437600083820152604051601f909101601f1916909201829003965090945050505050a150505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561123757600080fd5b505afa15801561124b573d6000803e3d6000fd5b505050506040513d602081101561126157600080fd5b50516001600160a01b031633146112b65760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508692506112fa915083905082611bf7565b15156001146113485760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b61138987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250889250611ca7915050565b15156001146113e25760408051600160e51b62461bcd02815260206004820152601260248201527f6e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b600061142388888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561143457fe5b60009182526020808320848452600a600b9093020191909101815260408083206001600160a01b038a16845290915290205460ff161515600114156114c35760408051600160e51b62461bcd02815260206004820152601260248201527f63616e6e6f7420646f75626c6520766f74650000000000000000000000000000604482015290519081900360640190fd5b60018054829081106114d157fe5b600091825260209091206003600b90920201018054600190810190915580548190839081106114fc57fe5b60009182526020808320858452600b92909202909101600a01815260408083206001600160a01b038b168452825291829020805460ff19169315159390931790925580518281529182018990527f87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508918a918a919081908101848480828437600083820152604051601f909101601f19169092018290039550909350505050a160026001828154811015156115ac57fe5b90600052602060002090600b0201600201548115156115c757fe5b046001828154811015156115d757fe5b90600052602060002090600b02016003015411156116e857604080516020810190915260008152600180548390811061160c57fe5b90600052602060002090600b02016004016000019080519060200190611633929190611d8c565b50604080516020810190915260008152600180548390811061165157fe5b90600052602060002090600b02016004016001019080519060200190611678929190611d8c565b50600060018281548110151561168a57fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b03939093169290921790915560018054839081106116ca57fe5b600091825260209091206007600b90920201015550600192506116ee565b60009350505b5050949350505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561174557600080fd5b505afa158015611759573d6000803e3d6000fd5b505050506040513d602081101561176f57600080fd5b50516001600160a01b031633146117c45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b61180388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509250611ca7915050565b151561184357604051600160e51b62461bcd028152600401808060200182810382526034815260200180611f1e6034913960400191505060405180910390fd5b600061188489898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050868660018381548110151561189757fe5b90600052602060002090600b020160040160000191906118b8929190611d0e565b5084846001838154811015156118ca57fe5b90600052602060002090600b020160040160010191906118eb929190611d0e565b50826001828154811015156118fc57fe5b90600052602060002090600b020160040160020160006101000a8154816001600160a01b0302191690836001600160a01b031602179055508160018281548110151561194457fe5b6000918252602082206007600b9092020101919091555b600180548390811061196957fe5b90600052602060002090600b020160080180549050811015611a6b57600180548390811061199357fe5b90600052602060002090600b0201600801818154811015156119b157fe5b600091825260209091200154600160a01b900460ff1615611a635760006001838154811015156119dd57fe5b90600052602060002090600b0201600a0160008481526020019081526020016000206000600185815481101515611a1057fe5b90600052602060002090600b020160080184815481101515611a2e57fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff19169115159190911790555b60010161195b565b506000600182815481101515611a7d57fe5b90600052602060002090600b0201600301819055507f5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3898960405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a1505050505050505050565b6000600160026000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611b46578181015183820152602001611b2e565b50505050905090810190601f168015611b735780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b600080611bb184611afd565b905060018082815481101515611bc357fe5b600091825260208083206001600160a01b03881684526009600b909302019190910190526040902054039150505b92915050565b600080611c0384611afd565b9050600181815481101515611c1457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020541515611c4d576000915050611bf1565b6000611c598585611ba5565b9050600182815481101515611c6a57fe5b90600052602060002090600b020160080181815481101515611c8857fe5b600091825260209091200154600160a01b900460ff1695945050505050565b6000816001611cb585611afd565b81548110611cbf57fe5b90600052602060002090600b02016004016003015414905092915050565b815481835581811115611d0957600b0281600b028360005260206000209182019101611d099190611dfa565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611d4f5782800160ff19823516178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578235825591602001919060010190611d61565b50611d88929150611e7f565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611dcd57805160ff1916838001178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578251825591602001919060010190611ddf565b611e7c91905b80821115611d88576000611e148282611e99565b60006001830181905560028301819055600383018190556004830190611e3a8282611e99565b611e48600183016000611e99565b506002810180546001600160a01b031916905560006003909101819055611e73906008840190611ee0565b50600b01611e00565b90565b611e7c91905b80821115611d885760008155600101611e85565b50805460018160011615610100020316600290046000825580601f10611ebf5750611edd565b601f016020900490600052602060002090810190611edd9190611e7f565b50565b5080546000825590600052602060002090810190611edd9190611e7c91905b80821115611d885780546001600160a81b0319168155600101611eff56fe6974656d732070656e64696e6720666f7220617070726f76616c2e206e6577206974656d2063616e6e6f74206265206164646564a165627a7a723058208c26d91675a875844a1887c5ed36a158ac303cb35eb5df6294460bcd07c299780029 \ No newline at end of file diff --git a/permission/v1/contract/gen/gen.go b/permission/v1/contract/gen/gen.go new file mode 100644 index 0000000000..1c622e2a16 --- /dev/null +++ b/permission/v1/contract/gen/gen.go @@ -0,0 +1,27 @@ +// Quorum +// +// this is to generate go binding for smart contracts used in permissioning +// +// Require: +// 1. solc 0.5.4 +// 2. abigen (make all from root) + +//go:generate solc --abi --bin -o . --overwrite ../AccountManager.sol +//go:generate solc --abi --bin -o . --overwrite ../NodeManager.sol +//go:generate solc --abi --bin -o . --overwrite ../OrgManager.sol +//go:generate solc --abi --bin -o . --overwrite ../PermissionsImplementation.sol +//go:generate solc --abi --bin -o . --overwrite ../PermissionsInterface.sol +//go:generate solc --abi --bin -o . --overwrite ../PermissionsUpgradable.sol +//go:generate solc --abi --bin -o . --overwrite ../RoleManager.sol +//go:generate solc --abi --bin -o . --overwrite ../VoterManager.sol + +//go:generate abigen -pkg bind -abi ./AccountManager.abi -bin ./AccountManager.bin -type AcctManager -out ../../bind/accounts.go +//go:generate abigen -pkg bind -abi ./NodeManager.abi -bin ./NodeManager.bin -type NodeManager -out ../../bind/nodes.go +//go:generate abigen -pkg bind -abi ./OrgManager.abi -bin ./OrgManager.bin -type OrgManager -out ../../bind/org.go +//go:generate abigen -pkg bind -abi ./PermissionsImplementation.abi -bin ./PermissionsImplementation.bin -type PermImpl -out ../../bind/permission_impl.go +//go:generate abigen -pkg bind -abi ./PermissionsInterface.abi -bin ./PermissionsInterface.bin -type PermInterface -out ../../bind/permission_interface.go +//go:generate abigen -pkg bind -abi ./PermissionsUpgradable.abi -bin ./PermissionsUpgradable.bin -type permUpgr -out ../../bind/permission_upgr.go +//go:generate abigen -pkg bind -abi ./RoleManager.abi -bin ./RoleManager.bin -type RoleManager -out ../../bind/roles.go +//go:generate abigen -pkg bind -abi ./VoterManager.abi -bin ./VoterManager.bin -type VoterManager -out ../../bind/voter.go + +package gen diff --git a/permission/v2/backend.go b/permission/v2/backend.go new file mode 100644 index 0000000000..c534d6064c --- /dev/null +++ b/permission/v2/backend.go @@ -0,0 +1,348 @@ +package v2 + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" + eb "github.com/ethereum/go-ethereum/permission/v2/bind" +) + +type Backend struct { + Ib ptype.InterfaceBackend + Contr *Init +} + +func (b *Backend) ManageAccountPermissions() error { + chAccessModified := make(chan *eb.AcctManagerAccountAccessModified) + chAccessRevoked := make(chan *eb.AcctManagerAccountAccessRevoked) + chStatusChanged := make(chan *eb.AcctManagerAccountStatusChanged) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + + if _, err := b.Contr.PermAcct.AcctManagerFilterer.WatchAccountAccessModified(opts, chAccessModified); err != nil { + return fmt.Errorf("failed AccountAccessModified: %v", err) + } + + if _, err := b.Contr.PermAcct.AcctManagerFilterer.WatchAccountAccessRevoked(opts, chAccessRevoked); err != nil { + return fmt.Errorf("failed AccountAccessRevoked: %v", err) + } + + if _, err := b.Contr.PermAcct.AcctManagerFilterer.WatchAccountStatusChanged(opts, chStatusChanged); err != nil { + return fmt.Errorf("failed AccountStatusChanged: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtAccessModified := <-chAccessModified: + core.AcctInfoMap.UpsertAccount(evtAccessModified.OrgId, evtAccessModified.RoleId, evtAccessModified.Account, evtAccessModified.OrgAdmin, core.AcctStatus(int(evtAccessModified.Status.Uint64()))) + + case evtAccessRevoked := <-chAccessRevoked: + core.AcctInfoMap.UpsertAccount(evtAccessRevoked.OrgId, evtAccessRevoked.RoleId, evtAccessRevoked.Account, evtAccessRevoked.OrgAdmin, core.AcctActive) + + case evtStatusChanged := <-chStatusChanged: + if ac, err := core.AcctInfoMap.GetAccount(evtStatusChanged.Account); ac != nil { + core.AcctInfoMap.UpsertAccount(evtStatusChanged.OrgId, ac.RoleId, evtStatusChanged.Account, ac.IsOrgAdmin, core.AcctStatus(int(evtStatusChanged.Status.Uint64()))) + } else { + log.Info("error fetching account information", "err", err) + } + case <-stopChan: + log.Info("quit account contract watch") + return + } + } + }() + return nil +} + +func (b *Backend) ManageRolePermissions() error { + chRoleCreated := make(chan *eb.RoleManagerRoleCreated, 1) + chRoleRevoked := make(chan *eb.RoleManagerRoleRevoked, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + + if _, err := b.Contr.PermRole.RoleManagerFilterer.WatchRoleCreated(opts, chRoleCreated); err != nil { + return fmt.Errorf("failed WatchRoleCreated: %v", err) + } + + if _, err := b.Contr.PermRole.RoleManagerFilterer.WatchRoleRevoked(opts, chRoleRevoked); err != nil { + return fmt.Errorf("failed WatchRoleRevoked: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtRoleCreated := <-chRoleCreated: + core.RoleInfoMap.UpsertRole(evtRoleCreated.OrgId, evtRoleCreated.RoleId, evtRoleCreated.IsVoter, evtRoleCreated.IsAdmin, core.AccessType(int(evtRoleCreated.BaseAccess.Uint64())), true) + + case evtRoleRevoked := <-chRoleRevoked: + if r, _ := core.RoleInfoMap.GetRole(evtRoleRevoked.OrgId, evtRoleRevoked.RoleId); r != nil { + core.RoleInfoMap.UpsertRole(evtRoleRevoked.OrgId, evtRoleRevoked.RoleId, r.IsVoter, r.IsAdmin, r.Access, false) + } else { + log.Error("Revoke role - cache is missing role", "org", evtRoleRevoked.OrgId, "role", evtRoleRevoked.RoleId) + } + case <-stopChan: + log.Info("quit role contract watch") + return + } + } + }() + return nil +} + +func (b *Backend) ManageOrgPermissions() error { + chPendingApproval := make(chan *eb.OrgManagerOrgPendingApproval, 1) + chOrgApproved := make(chan *eb.OrgManagerOrgApproved, 1) + chOrgSuspended := make(chan *eb.OrgManagerOrgSuspended, 1) + chOrgReactivated := make(chan *eb.OrgManagerOrgSuspensionRevoked, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + + if _, err := b.Contr.PermOrg.OrgManagerFilterer.WatchOrgPendingApproval(opts, chPendingApproval); err != nil { + return fmt.Errorf("failed WatchOrgPendingApproval: %v", err) + } + + if _, err := b.Contr.PermOrg.OrgManagerFilterer.WatchOrgApproved(opts, chOrgApproved); err != nil { + return fmt.Errorf("failed WatchOrgApproved: %v", err) + } + + if _, err := b.Contr.PermOrg.OrgManagerFilterer.WatchOrgSuspended(opts, chOrgSuspended); err != nil { + return fmt.Errorf("failed WatchOrgSuspended: %v", err) + } + + if _, err := b.Contr.PermOrg.OrgManagerFilterer.WatchOrgSuspensionRevoked(opts, chOrgReactivated); err != nil { + return fmt.Errorf("failed WatchOrgSuspensionRevoked: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtPendingApproval := <-chPendingApproval: + core.OrgInfoMap.UpsertOrg(evtPendingApproval.OrgId, evtPendingApproval.PorgId, evtPendingApproval.UltParent, evtPendingApproval.Level, core.OrgStatus(evtPendingApproval.Status.Uint64())) + + case evtOrgApproved := <-chOrgApproved: + core.OrgInfoMap.UpsertOrg(evtOrgApproved.OrgId, evtOrgApproved.PorgId, evtOrgApproved.UltParent, evtOrgApproved.Level, core.OrgApproved) + + case evtOrgSuspended := <-chOrgSuspended: + core.OrgInfoMap.UpsertOrg(evtOrgSuspended.OrgId, evtOrgSuspended.PorgId, evtOrgSuspended.UltParent, evtOrgSuspended.Level, core.OrgSuspended) + + case evtOrgReactivated := <-chOrgReactivated: + core.OrgInfoMap.UpsertOrg(evtOrgReactivated.OrgId, evtOrgReactivated.PorgId, evtOrgReactivated.UltParent, evtOrgReactivated.Level, core.OrgApproved) + case <-stopChan: + log.Info("quit org contract watch") + return + } + } + }() + return nil +} + +func (b *Backend) ManageNodePermissions() error { + chNodeApproved := make(chan *eb.NodeManagerNodeApproved, 1) + chNodeProposed := make(chan *eb.NodeManagerNodeProposed, 1) + chNodeDeactivated := make(chan *eb.NodeManagerNodeDeactivated, 1) + chNodeActivated := make(chan *eb.NodeManagerNodeActivated, 1) + chNodeBlacklisted := make(chan *eb.NodeManagerNodeBlacklisted) + chNodeRecoveryInit := make(chan *eb.NodeManagerNodeRecoveryInitiated, 1) + chNodeRecoveryDone := make(chan *eb.NodeManagerNodeRecoveryCompleted, 1) + + opts := &bind.WatchOpts{} + var blockNumber uint64 = 1 + opts.Start = &blockNumber + + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeApproved(opts, chNodeApproved); err != nil { + return fmt.Errorf("failed WatchNodeApproved: %v", err) + } + + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeProposed(opts, chNodeProposed); err != nil { + return fmt.Errorf("failed WatchNodeProposed: %v", err) + } + + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeDeactivated(opts, chNodeDeactivated); err != nil { + return fmt.Errorf("failed NodeDeactivated: %v", err) + } + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeActivated(opts, chNodeActivated); err != nil { + return fmt.Errorf("failed WatchNodeActivated: %v", err) + } + + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeBlacklisted(opts, chNodeBlacklisted); err != nil { + return fmt.Errorf("failed NodeBlacklisting: %v", err) + } + + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeRecoveryInitiated(opts, chNodeRecoveryInit); err != nil { + return fmt.Errorf("failed NodeRecoveryInitiated: %v", err) + } + + if _, err := b.Contr.PermNode.NodeManagerFilterer.WatchNodeRecoveryCompleted(opts, chNodeRecoveryDone); err != nil { + return fmt.Errorf("failed NodeRecoveryCompleted: %v", err) + } + + go func() { + stopChan, stopSubscription := ptype.SubscribeStopEvent() + defer stopSubscription.Unsubscribe() + for { + select { + case evtNodeApproved := <-chNodeApproved: + enodeId := core.GetNodeUrl(evtNodeApproved.EnodeId, evtNodeApproved.Ip[:], evtNodeApproved.Port, evtNodeApproved.Raftport, b.Ib.IsRaft()) + err := ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), enodeId, ptype.NodeAdd, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + core.NodeInfoMap.UpsertNode(evtNodeApproved.OrgId, enodeId, core.NodeApproved) + + case evtNodeProposed := <-chNodeProposed: + enodeId := core.GetNodeUrl(evtNodeProposed.EnodeId, evtNodeProposed.Ip[:], evtNodeProposed.Port, evtNodeProposed.Raftport, b.Ib.IsRaft()) + core.NodeInfoMap.UpsertNode(evtNodeProposed.OrgId, enodeId, core.NodePendingApproval) + + case evtNodeDeactivated := <-chNodeDeactivated: + enodeId := core.GetNodeUrl(evtNodeDeactivated.EnodeId, evtNodeDeactivated.Ip[:], evtNodeDeactivated.Port, evtNodeDeactivated.Raftport, b.Ib.IsRaft()) + err := ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), enodeId, ptype.NodeDelete, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + core.NodeInfoMap.UpsertNode(evtNodeDeactivated.OrgId, enodeId, core.NodeDeactivated) + + case evtNodeActivated := <-chNodeActivated: + enodeId := core.GetNodeUrl(evtNodeActivated.EnodeId, evtNodeActivated.Ip[:], evtNodeActivated.Port, evtNodeActivated.Raftport, b.Ib.IsRaft()) + err := ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), enodeId, ptype.NodeAdd, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + core.NodeInfoMap.UpsertNode(evtNodeActivated.OrgId, enodeId, core.NodeApproved) + + case evtNodeBlacklisted := <-chNodeBlacklisted: + enodeId := core.GetNodeUrl(evtNodeBlacklisted.EnodeId, evtNodeBlacklisted.Ip[:], evtNodeBlacklisted.Port, evtNodeBlacklisted.Raftport, b.Ib.IsRaft()) + core.NodeInfoMap.UpsertNode(evtNodeBlacklisted.OrgId, enodeId, core.NodeBlackListed) + err := ptype.UpdateDisallowedNodes(b.Ib.DataDir(), enodeId, ptype.NodeAdd) + log.Error("error updating disallowed-nodes.json", "err", err) + err = ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), enodeId, ptype.NodeDelete, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + + case evtNodeRecoveryInit := <-chNodeRecoveryInit: + enodeId := core.GetNodeUrl(evtNodeRecoveryInit.EnodeId, evtNodeRecoveryInit.Ip[:], evtNodeRecoveryInit.Port, evtNodeRecoveryInit.Raftport, b.Ib.IsRaft()) + core.NodeInfoMap.UpsertNode(evtNodeRecoveryInit.OrgId, enodeId, core.NodeRecoveryInitiated) + + case evtNodeRecoveryDone := <-chNodeRecoveryDone: + enodeId := core.GetNodeUrl(evtNodeRecoveryDone.EnodeId, evtNodeRecoveryDone.Ip[:], evtNodeRecoveryDone.Port, evtNodeRecoveryDone.Raftport, b.Ib.IsRaft()) + core.NodeInfoMap.UpsertNode(evtNodeRecoveryDone.OrgId, enodeId, core.NodeApproved) + err := ptype.UpdateDisallowedNodes(b.Ib.DataDir(), enodeId, ptype.NodeDelete) + log.Error("error updating disallowed-nodes.json", "err", err) + err = ptype.UpdatePermissionedNodes(b.Ib.Node(), b.Ib.DataDir(), enodeId, ptype.NodeAdd, b.Ib.IsRaft()) + if err != nil { + log.Error("error updating permissioned-nodes.json", "err", err) + } + + case <-stopChan: + log.Info("quit Node contract watch") + return + } + } + }() + return nil +} +func (b *Backend) MonitorNetworkBootUp() error { + return nil +} + +func getBackend(contractBackend ptype.ContractBackend) (*PermissionModelV2, error) { + backend := PermissionModelV2{ContractBackend: contractBackend} + ps, err := getInterfaceContractSession(backend.PermInterf, contractBackend.PermConfig.InterfAddress, contractBackend.EthClnt) + if err != nil { + return nil, err + } + backend.PermInterfSession = ps + return &backend, nil +} + +func getBackendWithTransactOpts(contractBackend ptype.ContractBackend, transactOpts *bind.TransactOpts) (*PermissionModelV2, error) { + backend, err := getBackend(contractBackend) + if err != nil { + return nil, err + } + backend.PermInterfSession.TransactOpts = *transactOpts + return backend, nil +} + +func getInterfaceContractSession(permInterfaceInstance *eb.PermInterface, contractAddress common.Address, backend bind.ContractBackend) (*eb.PermInterfaceSession, error) { + if err := ptype.BindContract(&permInterfaceInstance, func() (interface{}, error) { return eb.NewPermInterface(contractAddress, backend) }); err != nil { + return nil, err + } + ps := &eb.PermInterfaceSession{ + Contract: permInterfaceInstance, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + return ps, nil +} + +func (b *Backend) GetRoleService(transactOpts *bind.TransactOpts, roleBackend ptype.ContractBackend) (ptype.RoleService, error) { + + backEnd, err := getBackendWithTransactOpts(roleBackend, transactOpts) + if err != nil { + return nil, err + } + return &Role{Backend: backEnd}, nil + +} + +func (b *Backend) GetOrgService(transactOpts *bind.TransactOpts, orgBackend ptype.ContractBackend) (ptype.OrgService, error) { + backEnd, err := getBackendWithTransactOpts(orgBackend, transactOpts) + if err != nil { + return nil, err + } + return &Org{Backend: backEnd}, nil +} + +func (b *Backend) GetNodeService(transactOpts *bind.TransactOpts, nodeBackend ptype.ContractBackend) (ptype.NodeService, error) { + backEnd, err := getBackendWithTransactOpts(nodeBackend, transactOpts) + if err != nil { + return nil, err + } + return &Node{Backend: backEnd}, nil +} + +func (b *Backend) GetAccountService(transactOpts *bind.TransactOpts, accountBackend ptype.ContractBackend) (ptype.AccountService, error) { + backEnd, err := getBackendWithTransactOpts(accountBackend, transactOpts) + if err != nil { + return nil, err + } + return &Account{Backend: backEnd}, nil + +} + +func (b *Backend) GetAuditService(auditBackend ptype.ContractBackend) (ptype.AuditService, error) { + backEnd, err := getBackend(auditBackend) + if err != nil { + return nil, err + } + return &Audit{Backend: backEnd}, nil + +} + +func (b *Backend) GetControlService(controlBackend ptype.ContractBackend) (ptype.ControlService, error) { + backEnd, err := getBackend(controlBackend) + if err != nil { + return nil, err + } + return &Control{Backend: backEnd}, nil + +} diff --git a/permission/v2/bind/accounts.go b/permission/v2/bind/accounts.go new file mode 100644 index 0000000000..8af01882c2 --- /dev/null +++ b/permission/v2/bind/accounts.go @@ -0,0 +1,991 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// AcctManagerABI is the input ABI used to generate the binding from. +const AcctManagerABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_adminRole\",\"type\":\"bool\"}],\"name\":\"assignAccountRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"removeExistingAdmin\",\"outputs\":[{\"name\":\"voterUpdate\",\"type\":\"bool\"},{\"name\":\"account\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"getAccountDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfAccounts\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"getAccountOrgRole\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"validateAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"getAccountRole\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateAccountStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"orgAdminExists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_aIndex\",\"type\":\"uint256\"}],\"name\":\"getAccountDetailsFromIndex\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"addNewAdmin\",\"outputs\":[{\"name\":\"voterUpdate\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"}],\"name\":\"setDefaults\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"assignAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"checkOrgAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"getAccountStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgAdmin\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"AccountAccessModified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgAdmin\",\"type\":\"bool\"}],\"name\":\"AccountAccessRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"AccountStatusChanged\",\"type\":\"event\"}]" + +var AcctManagerParsedABI, _ = abi.JSON(strings.NewReader(AcctManagerABI)) + +// AcctManagerBin is the compiled bytecode used for deploying new contracts. +var AcctManagerBin = "0x608060405234801561001057600080fd5b50604051602080613d2d8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055613ccb806100626000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c806384b7a84a11610097578063cef7f6af11610066578063cef7f6af1461078c578063e3483a9d1461084a578063e8b42bf414610918578063fd4fa05a14610a51576100f5565b806384b7a84a146105d7578063950145cf14610654578063b2018568146106f8578063c214e5e514610715576100f5565b8063309e36ef116100d3578063309e36ef1461038c5780636acee5fd146103a65780636b568d76146104aa57806381d66b231461053c576100f5565b8063143a5604146100fa5780631d09dc93146101cc5780632aceb5341461025d575b600080fd5b6101ca6004803603608081101561011057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561013a57600080fd5b82018360208201111561014c57600080fd5b803590602001918460018302840111600160201b8311171561016d57600080fd5b919390929091602081019035600160201b81111561018a57600080fd5b82018360208201111561019c57600080fd5b803590602001918460018302840111600160201b831117156101bd57600080fd5b9193509150351515610a77565b005b61023a600480360360208110156101e257600080fd5b810190602081018135600160201b8111156101fc57600080fd5b82018360208201111561020e57600080fd5b803590602001918460018302840111600160201b8311171561022f57600080fd5b509092509050610e75565b6040805192151583526001600160a01b0390911660208301528051918290030190f35b6102836004803603602081101561027357600080fd5b50356001600160a01b0316611401565b60405180866001600160a01b03166001600160a01b03168152602001806020018060200185815260200184151515158152602001838103835287818151815260200191508051906020019080838360005b838110156102ec5781810151838201526020016102d4565b50505050905090810190601f1680156103195780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b8381101561034c578181015183820152602001610334565b50505050905090810190601f1680156103795780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b610394611659565b60408051918252519081900360200190f35b6103cc600480360360208110156103bc57600080fd5b50356001600160a01b0316611660565b604051808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561040d5781810151838201526020016103f5565b50505050905090810190601f16801561043a5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561046d578181015183820152602001610455565b50505050905090810190601f16801561049a5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390f35b610528600480360360408110156104c057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156104ea57600080fd5b8201836020820111156104fc57600080fd5b803590602001918460018302840111600160201b8311171561051d57600080fd5b509092509050611831565b604080519115158252519081900360200190f35b6105626004803603602081101561055257600080fd5b50356001600160a01b031661198c565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561059c578181015183820152602001610584565b50505050905090810190601f1680156105c95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101ca600480360360608110156105ed57600080fd5b810190602081018135600160201b81111561060757600080fd5b82018360208201111561061957600080fd5b803590602001918460018302840111600160201b8311171561063a57600080fd5b91935091506001600160a01b038135169060200135611ae2565b6105286004803603602081101561066a57600080fd5b810190602081018135600160201b81111561068457600080fd5b82018360208201111561069657600080fd5b803590602001918460018302840111600160201b831117156106b757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612215945050505050565b6102836004803603602081101561070e57600080fd5b5035612390565b6105286004803603604081101561072b57600080fd5b810190602081018135600160201b81111561074557600080fd5b82018360208201111561075757600080fd5b803590602001918460018302840111600160201b8311171561077857600080fd5b9193509150356001600160a01b031661257a565b6101ca600480360360408110156107a257600080fd5b810190602081018135600160201b8111156107bc57600080fd5b8201836020820111156107ce57600080fd5b803590602001918460018302840111600160201b831117156107ef57600080fd5b919390929091602081019035600160201b81111561080c57600080fd5b82018360208201111561081e57600080fd5b803590602001918460018302840111600160201b8311171561083f57600080fd5b509092509050612bcc565b6101ca6004803603608081101561086057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561088a57600080fd5b82018360208201111561089c57600080fd5b803590602001918460018302840111600160201b831117156108bd57600080fd5b919390929091602081019035600160201b8111156108da57600080fd5b8201836020820111156108ec57600080fd5b803590602001918460018302840111600160201b8311171561090d57600080fd5b919350915035612cb8565b6105286004803603606081101561092e57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561095857600080fd5b82018360208201111561096a57600080fd5b803590602001918460018302840111600160201b8311171561098b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156109dd57600080fd5b8201836020820111156109ef57600080fd5b803590602001918460018302840111600160201b83111715610a1057600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061302a945050505050565b61039460048036036020811015610a6757600080fd5b50356001600160a01b0316613591565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610ac457600080fd5b505afa158015610ad8573d6000803e3d6000fd5b505050506040513d6020811015610aee57600080fd5b50516001600160a01b03163314610b435760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014158015610db457506040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015610cc15780601f10610c9657610100808354040283529160200191610cc1565b820191906000526020600020905b815481529060010190602001808311610ca457829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040526040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610d69578181015183820152602001610d51565b50505050905090810190601f168015610d965780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012014155b1515610df457604051600160e51b62461bcd028152600401808060200182810382526040815260200180613bfb6040913960400191505060405180910390fd5b610e6d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250600292508791506135ee9050565b505050505050565b6000805460408051600160e41b62e32cf9028152905183926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610ebc57600080fd5b505afa158015610ed0573d6000803e3d6000fd5b505050506040513d6020811015610ee657600080fd5b50516001600160a01b03163314610f3b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b610f7a84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061221592505050565b156113f357600061100260066000878760405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a90046001600160a01b03166139c8565b9050600660018281548110151561101557fe5b906000526020600020906005020160030181905550600060018281548110151561103b57fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc77660018281548110151561109357fe5b6000918252602090912060059091020154600180546001600160a01b0390921691849081106110be57fe5b90600052602060002090600502016001016001848154811015156110de57fe5b90600052602060002090600502016002016001858154811015156110fe57fe5b60009182526020909120600460059092020101546001805460ff909216918790811061112657fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156111d95780601f106111ae576101008083540402835291602001916111d9565b820191906000526020600020905b8154815290600101906020018083116111bc57829003601f168201915b505083810382528654600260001961010060018416150201909116048082526020909101908790801561124d5780601f106112225761010080835404028352916020019161124d565b820191906000526020600020905b81548152906001019060200180831161123057829003601f168201915b505097505050505050505060405180910390a160408051602080820190815260048054600260001961010060018416150201909116049383018490529290918291606090910190849080156112e35780601f106112b8576101008083540402835291602001916112e3565b820191906000526020600020905b8154815290600101906020018083116112c657829003601f168201915b5050925050506040516020818303038152906040528051906020012060018281548110151561130e57fe5b6000918252602091829020604080518085019485526002600590940290920183018054600019610100600183161502011693909304908201819052919291829160600190849080156113a15780601f10611376576101008083540402835291602001916113a1565b820191906000526020600020905b81548152906001019060200180831161138457829003601f168201915b50509250505060405160208183030381529060405280519060200120146001828154811015156113cd57fe5b60009182526020909120600590910201549093506001600160a01b031691506113fa9050565b5060009050805b9250929050565b6001600160a01b038116600090815260026020526040812054606090819083908190151561146857505060408051808201825260048152600160e01b634e4f4e45026020808301919091528251908101909252600080835286955090935090915080611650565b6000611473876139c8565b905060018181548110151561148457fe5b6000918252602090912060059091020154600180546001600160a01b0390921691839081106114af57fe5b90600052602060002090600502016001016001838154811015156114cf57fe5b90600052602060002090600502016002016001848154811015156114ef57fe5b90600052602060002090600502016003015460018581548110151561151057fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff909216928691908301828280156115af5780601f10611584576101008083540402835291602001916115af565b820191906000526020600020905b81548152906001019060200180831161159257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529599508894509250840190508282801561163d5780601f106116125761010080835404028352916020019161163d565b820191906000526020600020905b81548152906001019060200180831161162057829003601f168201915b5050505050925095509550955095509550505b91939590929450565b6001545b90565b6001600160a01b038116600090815260026020526040902054606090819015156116bd57604051806040016040528060048152602001600160e01b634e4f4e4502815250604051806020016040528060008152509150915061182c565b60006116c8846139c8565b90506001818154811015156116d957fe5b90600052602060002090600502016001016001828154811015156116f957fe5b6000918252602091829020835460408051601f600260001961010060018716150201909416849004908101879004870282018701909252818152600590940290920101928491908301828280156117915780601f1061176657610100808354040283529160200191611791565b820191906000526020600020905b81548152906001019060200180831161177457829003601f168201915b5050845460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529597508694509250840190508282801561181f5780601f106117f45761010080835404028352916020019161181f565b820191906000526020600020905b81548152906001019060200180831161180257829003601f168201915b5050505050905092509250505b915091565b6001600160a01b038316600090815260026020526040812054151561185857506001611985565b6000611863856139c8565b9050838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001206001828154811015156118c757fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156119645780601f1061193957610100808354040283529160200191611964565b820191906000526020600020905b81548152906001019060200180831161194757829003601f168201915b50509250505060405160208183030381529060405280519060200120149150505b9392505050565b6001600160a01b03811660009081526002602052604090205460609015156119d257506040805180820190915260048152600160e01b634e4f4e45026020820152611add565b60006119dd836139c8565b90506001818154811015156119ee57fe5b9060005260206000209060050201600301546000141515611abc576001805482908110611a1757fe5b600091825260209182902060026005909202018101805460408051601f600019610100600186161502019093169490940491820185900485028401850190528083529192909190830182828015611aaf5780601f10611a8457610100808354040283529160200191611aaf565b820191906000526020600020905b815481529060010190602001808311611a9257829003601f168201915b5050505050915050611add565b50506040805180820190915260048152600160e01b634e4f4e450260208201525b919050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611b2f57600080fd5b505afa158015611b43573d6000803e3d6000fd5b505050506040513d6020811015611b5957600080fd5b50516001600160a01b03163314611bae5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506001600160a01b03871681526002602052604090205486935015159150611c5390505760408051600160e51b62461bcd02815260206004820152601760248201527f6163636f756e7420646f6573206e6f7420657869737473000000000000000000604482015290519081900360640190fd5b816040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611c94578181015183820152602001611c7c565b50505050905090810190601f168015611cc15780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001206001611ce7836139c8565b81548110611cf157fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611d8e5780601f10611d6357610100808354040283529160200191611d8e565b820191906000526020600020905b815481529060010190602001808311611d7157829003601f168201915b50509250505060405160208183030381529060405280519060200120141515611e015760408051600160e51b62461bcd02815260206004820152601860248201527f6163636f756e7420696e20646966666572656e74206f72670000000000000000604482015290519081900360640190fd5b600083118015611e115750600683105b1515611e675760408051600160e51b62461bcd02815260206004820152601d60248201527f696e76616c696420737461747573206368616e67652072657175657374000000604482015290519081900360640190fd5b611eb58487878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506040805160208101909152908152925061302a915050565b151560011415611ef957604051600160e51b62461bcd028152600401808060200182810382526031815260200180613c6f6031913960400191505060405180910390fd5b60008360011415611f76576001611f0f866139c8565b81548110611f1957fe5b9060005260206000209060050201600301546002141515611f6e57604051600160e51b62461bcd028152600401808060200182810382526039815260200180613b9a6039913960400191505060405180910390fd5b50600461215f565b8360021415611ff1576001611f8a866139c8565b81548110611f9457fe5b9060005260206000209060050201600301546004141515611fe957604051600160e51b62461bcd02815260040180806020018281038252603c815260200180613b5e603c913960400191505060405180910390fd5b50600261215f565b836003141561206d576001612005866139c8565b8154811061200f57fe5b90600052602060002090600502016003015460051415151561206557604051600160e51b62461bcd028152600401808060200182810382526038815260200180613b266038913960400191505060405180910390fd5b50600561215f565b83600414156120e8576001612081866139c8565b8154811061208b57fe5b90600052602060002090600502016003015460051415156120e057604051600160e51b62461bcd028152600401808060200182810382526034815260200180613c3b6034913960400191505060405180910390fd5b50600761215f565b836005141561215f5760016120fc866139c8565b8154811061210657fe5b906000526020600020906005020160030154600714151561215b57604051600160e51b62461bcd028152600401808060200182810382526038815260200180613aee6038913960400191505060405180910390fd5b5060025b80600161216b876139c8565b8154811061217557fe5b9060005260206000209060050201600301819055507f36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b258588888460405180856001600160a01b03166001600160a01b03168152602001806020018381526020018281038252858582818152602001925080828437600083820152604051601f909101601f191690920182900397509095505050505050a150505050505050565b6000806001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561226657818101518382015260200161224e565b50505050905090810190601f1680156122935780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b03161461238857600060066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156123125781810151838201526020016122fa565b50505050905090810190601f16801561233f5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316905061237d81613591565b600214915050611add565b506000919050565b60006060806000806001868154811015156123a757fe5b6000918252602090912060059091020154600180546001600160a01b0390921691889081106123d257fe5b90600052602060002090600502016001016001888154811015156123f257fe5b906000526020600020906005020160020160018981548110151561241257fe5b90600052602060002090600502016003015460018a81548110151561243357fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff909216928691908301828280156124d25780601f106124a7576101008083540402835291602001916124d2565b820191906000526020600020905b8154815290600101906020018083116124b557829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156125605780601f1061253557610100808354040283529160200191612560565b820191906000526020600020905b81548152906001019060200180831161254357829003601f168201915b505050505092509450945094509450945091939590929450565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156125c957600080fd5b505afa1580156125dd573d6000803e3d6000fd5b505050506040513d60208110156125f357600080fd5b50516001600160a01b031633146126485760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60606126538361198c565b9050600061266084613591565b9050600061266d856139c8565b604080516020808201908152600580546002600019610100600184161502019091160493830184905293945091829160600190849080156126ef5780601f106126c4576101008083540402835291602001916126ef565b820191906000526020600020905b8154815290600101906020018083116126d257829003601f168201915b50509250505060405160208183030381529060405280519060200120836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561274c578181015183820152602001612734565b50505050905090810190601f1680156127795780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001201480156127a15750816001145b15612831578460066000898960405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b600260018281548110151561284257fe5b9060005260206000209060050201600301819055506001808281548110151561286757fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776856001838154811015156128c057fe5b90600052602060002090600502016001016001848154811015156128e057fe5b906000526020600020906005020160020160018581548110151561290057fe5b60009182526020909120600460059092020101546001805460ff909216918790811061292857fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156129db5780601f106129b0576101008083540402835291602001916129db565b820191906000526020600020905b8154815290600101906020018083116129be57829003601f168201915b5050838103825286546002600019610100600184161502019091160480825260209091019087908015612a4f5780601f10612a2457610100808354040283529160200191612a4f565b820191906000526020600020905b815481529060010190602001808311612a3257829003601f168201915b505097505050505050505060405180910390a16040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015612ae55780601f10612aba57610100808354040283529160200191612ae5565b820191906000526020600020905b815481529060010190602001808311612ac857829003601f168201915b50509250505060405160208183030381529060405280519060200120600182815481101515612b1057fe5b600091825260209182902060408051808501948552600260059094029092018301805460001961010060018316150201169390930490820181905291929182916060019084908015612ba35780601f10612b7857610100808354040283529160200191612ba3565b820191906000526020600020905b815481529060010190602001808311612b8657829003601f168201915b505092505050604051602081830303815290604052805190602001201493505050509392505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015612c1957600080fd5b505afa158015612c2d573d6000803e3d6000fd5b505050506040513d6020811015612c4357600080fd5b50516001600160a01b03163314612c985760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b612ca4600485856139e7565b50612cb1600583836139e7565b5050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015612d0557600080fd5b505afa158015612d19573d6000803e3d6000fd5b505050506040513d6020811015612d2f57600080fd5b50516001600160a01b03163314612d845760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015612e075780601f10612ddc57610100808354040283529160200191612e07565b820191906000526020600020905b815481529060010190602001808311612dea57829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001201480612f7157506040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015612f005780601f10612ed557610100808354040283529160200191612f00565b820191906000526020600020905b815481529060010190602001808311612ee357829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120145b1515612fb157604051600160e51b62461bcd028152600401808060200182810382526028815260200180613bd36028913960400191505060405180910390fd5b610e6d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250879250600191506135ee9050565b604080516020808201908152600480546002600019610100600184161502019091160493830184905260009390928291606090910190849080156130af5780601f10613084576101008083540402835291602001916130af565b820191906000526020600020905b81548152906001019060200180831161309257829003601f168201915b505092505050604051602081830303815290604052805190602001206130d48561198c565b6040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156131145781810151838201526020016130fc565b50505050905090810190601f1680156131415780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120141561342257600061316d856139c8565b9050836040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156131b0578181015183820152602001613198565b50505050905090810190601f1680156131dd5780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561320757fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156132a45780601f10613279576101008083540402835291602001916132a4565b820191906000526020600020905b81548152906001019060200180831161328757829003601f168201915b50509250505060405160208183030381529060405280519060200120148061341a5750826040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156133085781810151838201526020016132f0565b50505050905090810190601f1680156133355780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561335f57fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156133fc5780601f106133d1576101008083540402835291602001916133fc565b820191906000526020600020905b8154815290600101906020018083116133df57829003601f168201915b50509250505060405160208183030381529060405280519060200120145b915050611985565b836001600160a01b031660066000856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015613471578181015183820152602001613459565b50505050905090810190601f16801561349e5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b031614806135895750836001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561352757818101518382015260200161350f565b50505050905090810190601f1680156135545780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316145b949350505050565b6001600160a01b03811660009081526002602052604081205415156135b857506000611add565b60006135c3836139c8565b90506001818154811015156135d457fe5b906000526020600020906005020160030154915050919050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561363b57600080fd5b505afa15801561364f573d6000803e3d6000fd5b505050506040513d602081101561366557600080fd5b50516001600160a01b031633146136ba5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60006136c5866139c8565b6001600160a01b0387166000908152600260205260409020549091501561377757836001828154811015156136f657fe5b9060005260206000209060050201600201908051906020019061371a929190613a65565b508260018281548110151561372b57fe5b9060005260206000209060050201600301819055508160018281548110151561375057fe5b60009182526020909120600590910201600401805460ff1916911515919091179055613892565b600380546001908101918290556001600160a01b03888116600081815260026020908152604080832096909655855160a0810187529283528281018b81529583018a905260608301899052871515608084015284548086018087559590925282517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6600590930292830180546001600160a01b03191691909516178455945180519495929461384f937fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7909301929190910190613a65565b506040820151805161386b916002840191602090910190613a65565b50606082015160038201556080909101516004909101805460ff1916911515919091179055505b7f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776868686858760405180866001600160a01b03166001600160a01b03168152602001806020018060200185151515158152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015613921578181015183820152602001613909565b50505050905090810190601f16801561394e5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015613981578181015183820152602001613969565b50505050905090810190601f1680156139ae5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a1505050505050565b6001600160a01b03166000908152600260205260409020546000190190565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613a285782800160ff19823516178555613a55565b82800160010185558215613a55579182015b82811115613a55578235825591602001919060010190613a3a565b50613a61929150613ad3565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613aa657805160ff1916838001178555613a55565b82800160010185558215613a55579182015b82811115613a55578251825591602001919060010190613ab8565b61165d91905b80821115613a615760008155600101613ad956fe6163636f756e74207265636f76657279206e6f7420696e697469617465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e7420697320616c726561647920626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e2073757370656e646564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e20616374697665207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e6563616e2062652063616c6c656420746f2061737369676e2061646d696e20726f6c6573206f6e6c7963616e6e6f742062652063616c6c65642066726f2061737369676e696e67206f72672061646d696e20616e64206e6574776f726b2061646d696e20726f6c65736163636f756e74206973206e6f7420626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e65737461747573206368616e6765206e6f7420706f737369626c6520666f72206f72672061646d696e206163636f756e7473a165627a7a72305820f5cf4f43c37e9dae763b0a301bd310a91d3b733b1642aabb6208a69fcc6a272d0029" + +// DeployAcctManager deploys a new Ethereum contract, binding an instance of AcctManager to it. +func DeployAcctManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *AcctManager, error) { + parsed, err := abi.JSON(strings.NewReader(AcctManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(AcctManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &AcctManager{AcctManagerCaller: AcctManagerCaller{contract: contract}, AcctManagerTransactor: AcctManagerTransactor{contract: contract}, AcctManagerFilterer: AcctManagerFilterer{contract: contract}}, nil +} + +// AcctManager is an auto generated Go binding around an Ethereum contract. +type AcctManager struct { + AcctManagerCaller // Read-only binding to the contract + AcctManagerTransactor // Write-only binding to the contract + AcctManagerFilterer // Log filterer for contract events +} + +// AcctManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type AcctManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AcctManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type AcctManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AcctManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type AcctManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AcctManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type AcctManagerSession struct { + Contract *AcctManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// AcctManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type AcctManagerCallerSession struct { + Contract *AcctManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// AcctManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type AcctManagerTransactorSession struct { + Contract *AcctManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// AcctManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type AcctManagerRaw struct { + Contract *AcctManager // Generic contract binding to access the raw methods on +} + +// AcctManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type AcctManagerCallerRaw struct { + Contract *AcctManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// AcctManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type AcctManagerTransactorRaw struct { + Contract *AcctManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewAcctManager creates a new instance of AcctManager, bound to a specific deployed contract. +func NewAcctManager(address common.Address, backend bind.ContractBackend) (*AcctManager, error) { + contract, err := bindAcctManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &AcctManager{AcctManagerCaller: AcctManagerCaller{contract: contract}, AcctManagerTransactor: AcctManagerTransactor{contract: contract}, AcctManagerFilterer: AcctManagerFilterer{contract: contract}}, nil +} + +// NewAcctManagerCaller creates a new read-only instance of AcctManager, bound to a specific deployed contract. +func NewAcctManagerCaller(address common.Address, caller bind.ContractCaller) (*AcctManagerCaller, error) { + contract, err := bindAcctManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &AcctManagerCaller{contract: contract}, nil +} + +// NewAcctManagerTransactor creates a new write-only instance of AcctManager, bound to a specific deployed contract. +func NewAcctManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*AcctManagerTransactor, error) { + contract, err := bindAcctManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &AcctManagerTransactor{contract: contract}, nil +} + +// NewAcctManagerFilterer creates a new log filterer instance of AcctManager, bound to a specific deployed contract. +func NewAcctManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*AcctManagerFilterer, error) { + contract, err := bindAcctManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &AcctManagerFilterer{contract: contract}, nil +} + +// bindAcctManager binds a generic wrapper to an already deployed contract. +func bindAcctManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(AcctManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_AcctManager *AcctManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _AcctManager.Contract.AcctManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_AcctManager *AcctManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AcctManager.Contract.AcctManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_AcctManager *AcctManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AcctManager.Contract.AcctManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_AcctManager *AcctManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _AcctManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_AcctManager *AcctManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AcctManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_AcctManager *AcctManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AcctManager.Contract.contract.Transact(opts, method, params...) +} + +// CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. +// +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +func (_AcctManager *AcctManagerCaller) CheckOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "checkOrgAdmin", _account, _orgId, _ultParent) + return *ret0, err +} + +// CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. +// +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +func (_AcctManager *AcctManagerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { + return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) +} + +// CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. +// +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +func (_AcctManager *AcctManagerCallerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { + return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) +} + +// GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. +// +// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCaller) GetAccountDetails(opts *bind.CallOpts, _account common.Address) (common.Address, string, string, *big.Int, bool, error) { + var ( + ret0 = new(common.Address) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(bool) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _AcctManager.contract.Call(opts, out, "getAccountDetails", _account) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. +// +// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) +} + +// GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. +// +// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCallerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) +} + +// GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. +// +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCaller) GetAccountDetailsFromIndex(opts *bind.CallOpts, _aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + var ( + ret0 = new(common.Address) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(bool) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _AcctManager.contract.Call(opts, out, "getAccountDetailsFromIndex", _aIndex) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. +// +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) +} + +// GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. +// +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +func (_AcctManager *AcctManagerCallerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) +} + +// GetAccountOrgRole is a free data retrieval call binding the contract method 0x6acee5fd. +// +// Solidity: function getAccountOrgRole(address _account) constant returns(string, string) +func (_AcctManager *AcctManagerCaller) GetAccountOrgRole(opts *bind.CallOpts, _account common.Address) (string, string, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ) + out := &[]interface{}{ + ret0, + ret1, + } + err := _AcctManager.contract.Call(opts, out, "getAccountOrgRole", _account) + return *ret0, *ret1, err +} + +// GetAccountOrgRole is a free data retrieval call binding the contract method 0x6acee5fd. +// +// Solidity: function getAccountOrgRole(address _account) constant returns(string, string) +func (_AcctManager *AcctManagerSession) GetAccountOrgRole(_account common.Address) (string, string, error) { + return _AcctManager.Contract.GetAccountOrgRole(&_AcctManager.CallOpts, _account) +} + +// GetAccountOrgRole is a free data retrieval call binding the contract method 0x6acee5fd. +// +// Solidity: function getAccountOrgRole(address _account) constant returns(string, string) +func (_AcctManager *AcctManagerCallerSession) GetAccountOrgRole(_account common.Address) (string, string, error) { + return _AcctManager.Contract.GetAccountOrgRole(&_AcctManager.CallOpts, _account) +} + +// GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. +// +// Solidity: function getAccountRole(address _account) constant returns(string) +func (_AcctManager *AcctManagerCaller) GetAccountRole(opts *bind.CallOpts, _account common.Address) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "getAccountRole", _account) + return *ret0, err +} + +// GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. +// +// Solidity: function getAccountRole(address _account) constant returns(string) +func (_AcctManager *AcctManagerSession) GetAccountRole(_account common.Address) (string, error) { + return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) +} + +// GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. +// +// Solidity: function getAccountRole(address _account) constant returns(string) +func (_AcctManager *AcctManagerCallerSession) GetAccountRole(_account common.Address) (string, error) { + return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) +} + +// GetAccountStatus is a free data retrieval call binding the contract method 0xfd4fa05a. +// +// Solidity: function getAccountStatus(address _account) constant returns(uint256) +func (_AcctManager *AcctManagerCaller) GetAccountStatus(opts *bind.CallOpts, _account common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "getAccountStatus", _account) + return *ret0, err +} + +// GetAccountStatus is a free data retrieval call binding the contract method 0xfd4fa05a. +// +// Solidity: function getAccountStatus(address _account) constant returns(uint256) +func (_AcctManager *AcctManagerSession) GetAccountStatus(_account common.Address) (*big.Int, error) { + return _AcctManager.Contract.GetAccountStatus(&_AcctManager.CallOpts, _account) +} + +// GetAccountStatus is a free data retrieval call binding the contract method 0xfd4fa05a. +// +// Solidity: function getAccountStatus(address _account) constant returns(uint256) +func (_AcctManager *AcctManagerCallerSession) GetAccountStatus(_account common.Address) (*big.Int, error) { + return _AcctManager.Contract.GetAccountStatus(&_AcctManager.CallOpts, _account) +} + +// GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. +// +// Solidity: function getNumberOfAccounts() constant returns(uint256) +func (_AcctManager *AcctManagerCaller) GetNumberOfAccounts(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "getNumberOfAccounts") + return *ret0, err +} + +// GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. +// +// Solidity: function getNumberOfAccounts() constant returns(uint256) +func (_AcctManager *AcctManagerSession) GetNumberOfAccounts() (*big.Int, error) { + return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) +} + +// GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. +// +// Solidity: function getNumberOfAccounts() constant returns(uint256) +func (_AcctManager *AcctManagerCallerSession) GetNumberOfAccounts() (*big.Int, error) { + return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) +} + +// OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. +// +// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCaller) OrgAdminExists(opts *bind.CallOpts, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "orgAdminExists", _orgId) + return *ret0, err +} + +// OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. +// +// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerSession) OrgAdminExists(_orgId string) (bool, error) { + return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) +} + +// OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. +// +// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCallerSession) OrgAdminExists(_orgId string) (bool, error) { + return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _AcctManager.contract.Call(opts, out, "validateAccount", _account, _orgId) + return *ret0, err +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_AcctManager *AcctManagerCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) +} + +// AddNewAdmin is a paid mutator transaction binding the contract method 0xc214e5e5. +// +// Solidity: function addNewAdmin(string _orgId, address _account) returns(bool voterUpdate) +func (_AcctManager *AcctManagerTransactor) AddNewAdmin(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "addNewAdmin", _orgId, _account) +} + +// AddNewAdmin is a paid mutator transaction binding the contract method 0xc214e5e5. +// +// Solidity: function addNewAdmin(string _orgId, address _account) returns(bool voterUpdate) +func (_AcctManager *AcctManagerSession) AddNewAdmin(_orgId string, _account common.Address) (*types.Transaction, error) { + return _AcctManager.Contract.AddNewAdmin(&_AcctManager.TransactOpts, _orgId, _account) +} + +// AddNewAdmin is a paid mutator transaction binding the contract method 0xc214e5e5. +// +// Solidity: function addNewAdmin(string _orgId, address _account) returns(bool voterUpdate) +func (_AcctManager *AcctManagerTransactorSession) AddNewAdmin(_orgId string, _account common.Address) (*types.Transaction, error) { + return _AcctManager.Contract.AddNewAdmin(&_AcctManager.TransactOpts, _orgId, _account) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x143a5604. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, bool _adminRole) returns() +func (_AcctManager *AcctManagerTransactor) AssignAccountRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string, _adminRole bool) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "assignAccountRole", _account, _orgId, _roleId, _adminRole) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x143a5604. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, bool _adminRole) returns() +func (_AcctManager *AcctManagerSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _adminRole bool) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAccountRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _adminRole) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x143a5604. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, bool _adminRole) returns() +func (_AcctManager *AcctManagerTransactorSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _adminRole bool) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAccountRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _adminRole) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0xe3483a9d. +// +// Solidity: function assignAdminRole(address _account, string _orgId, string _roleId, uint256 _status) returns() +func (_AcctManager *AcctManagerTransactor) AssignAdminRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string, _status *big.Int) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "assignAdminRole", _account, _orgId, _roleId, _status) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0xe3483a9d. +// +// Solidity: function assignAdminRole(address _account, string _orgId, string _roleId, uint256 _status) returns() +func (_AcctManager *AcctManagerSession) AssignAdminRole(_account common.Address, _orgId string, _roleId string, _status *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAdminRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _status) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0xe3483a9d. +// +// Solidity: function assignAdminRole(address _account, string _orgId, string _roleId, uint256 _status) returns() +func (_AcctManager *AcctManagerTransactorSession) AssignAdminRole(_account common.Address, _orgId string, _roleId string, _status *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.AssignAdminRole(&_AcctManager.TransactOpts, _account, _orgId, _roleId, _status) +} + +// RemoveExistingAdmin is a paid mutator transaction binding the contract method 0x1d09dc93. +// +// Solidity: function removeExistingAdmin(string _orgId) returns(bool voterUpdate, address account) +func (_AcctManager *AcctManagerTransactor) RemoveExistingAdmin(opts *bind.TransactOpts, _orgId string) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "removeExistingAdmin", _orgId) +} + +// RemoveExistingAdmin is a paid mutator transaction binding the contract method 0x1d09dc93. +// +// Solidity: function removeExistingAdmin(string _orgId) returns(bool voterUpdate, address account) +func (_AcctManager *AcctManagerSession) RemoveExistingAdmin(_orgId string) (*types.Transaction, error) { + return _AcctManager.Contract.RemoveExistingAdmin(&_AcctManager.TransactOpts, _orgId) +} + +// RemoveExistingAdmin is a paid mutator transaction binding the contract method 0x1d09dc93. +// +// Solidity: function removeExistingAdmin(string _orgId) returns(bool voterUpdate, address account) +func (_AcctManager *AcctManagerTransactorSession) RemoveExistingAdmin(_orgId string) (*types.Transaction, error) { + return _AcctManager.Contract.RemoveExistingAdmin(&_AcctManager.TransactOpts, _orgId) +} + +// SetDefaults is a paid mutator transaction binding the contract method 0xcef7f6af. +// +// Solidity: function setDefaults(string _nwAdminRole, string _oAdminRole) returns() +func (_AcctManager *AcctManagerTransactor) SetDefaults(opts *bind.TransactOpts, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "setDefaults", _nwAdminRole, _oAdminRole) +} + +// SetDefaults is a paid mutator transaction binding the contract method 0xcef7f6af. +// +// Solidity: function setDefaults(string _nwAdminRole, string _oAdminRole) returns() +func (_AcctManager *AcctManagerSession) SetDefaults(_nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _AcctManager.Contract.SetDefaults(&_AcctManager.TransactOpts, _nwAdminRole, _oAdminRole) +} + +// SetDefaults is a paid mutator transaction binding the contract method 0xcef7f6af. +// +// Solidity: function setDefaults(string _nwAdminRole, string _oAdminRole) returns() +func (_AcctManager *AcctManagerTransactorSession) SetDefaults(_nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _AcctManager.Contract.SetDefaults(&_AcctManager.TransactOpts, _nwAdminRole, _oAdminRole) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_AcctManager *AcctManagerTransactor) UpdateAccountStatus(opts *bind.TransactOpts, _orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _AcctManager.contract.Transact(opts, "updateAccountStatus", _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_AcctManager *AcctManagerSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.UpdateAccountStatus(&_AcctManager.TransactOpts, _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_AcctManager *AcctManagerTransactorSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _AcctManager.Contract.UpdateAccountStatus(&_AcctManager.TransactOpts, _orgId, _account, _action) +} + +// AcctManagerAccountAccessModifiedIterator is returned from FilterAccountAccessModified and is used to iterate over the raw logs and unpacked data for AccountAccessModified events raised by the AcctManager contract. +type AcctManagerAccountAccessModifiedIterator struct { + Event *AcctManagerAccountAccessModified // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AcctManagerAccountAccessModifiedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessModified) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessModified) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AcctManagerAccountAccessModifiedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AcctManagerAccountAccessModifiedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AcctManagerAccountAccessModified represents a AccountAccessModified event raised by the AcctManager contract. +type AcctManagerAccountAccessModified struct { + Account common.Address + OrgId string + RoleId string + OrgAdmin bool + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAccountAccessModified is a free log retrieval operation binding the contract event 0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776. +// +// Solidity: event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint256 _status) +func (_AcctManager *AcctManagerFilterer) FilterAccountAccessModified(opts *bind.FilterOpts) (*AcctManagerAccountAccessModifiedIterator, error) { + + logs, sub, err := _AcctManager.contract.FilterLogs(opts, "AccountAccessModified") + if err != nil { + return nil, err + } + return &AcctManagerAccountAccessModifiedIterator{contract: _AcctManager.contract, event: "AccountAccessModified", logs: logs, sub: sub}, nil +} + +var AccountAccessModifiedTopicHash = "0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776" + +// WatchAccountAccessModified is a free log subscription operation binding the contract event 0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776. +// +// Solidity: event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint256 _status) +func (_AcctManager *AcctManagerFilterer) WatchAccountAccessModified(opts *bind.WatchOpts, sink chan<- *AcctManagerAccountAccessModified) (event.Subscription, error) { + + logs, sub, err := _AcctManager.contract.WatchLogs(opts, "AccountAccessModified") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AcctManagerAccountAccessModified) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessModified", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAccountAccessModified is a log parse operation binding the contract event 0x68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776. +// +// Solidity: event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint256 _status) +func (_AcctManager *AcctManagerFilterer) ParseAccountAccessModified(log types.Log) (*AcctManagerAccountAccessModified, error) { + event := new(AcctManagerAccountAccessModified) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessModified", log); err != nil { + return nil, err + } + return event, nil +} + +// AcctManagerAccountAccessRevokedIterator is returned from FilterAccountAccessRevoked and is used to iterate over the raw logs and unpacked data for AccountAccessRevoked events raised by the AcctManager contract. +type AcctManagerAccountAccessRevokedIterator struct { + Event *AcctManagerAccountAccessRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AcctManagerAccountAccessRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountAccessRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AcctManagerAccountAccessRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AcctManagerAccountAccessRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AcctManagerAccountAccessRevoked represents a AccountAccessRevoked event raised by the AcctManager contract. +type AcctManagerAccountAccessRevoked struct { + Account common.Address + OrgId string + RoleId string + OrgAdmin bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAccountAccessRevoked is a free log retrieval operation binding the contract event 0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1. +// +// Solidity: event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin) +func (_AcctManager *AcctManagerFilterer) FilterAccountAccessRevoked(opts *bind.FilterOpts) (*AcctManagerAccountAccessRevokedIterator, error) { + + logs, sub, err := _AcctManager.contract.FilterLogs(opts, "AccountAccessRevoked") + if err != nil { + return nil, err + } + return &AcctManagerAccountAccessRevokedIterator{contract: _AcctManager.contract, event: "AccountAccessRevoked", logs: logs, sub: sub}, nil +} + +var AccountAccessRevokedTopicHash = "0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1" + +// WatchAccountAccessRevoked is a free log subscription operation binding the contract event 0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1. +// +// Solidity: event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin) +func (_AcctManager *AcctManagerFilterer) WatchAccountAccessRevoked(opts *bind.WatchOpts, sink chan<- *AcctManagerAccountAccessRevoked) (event.Subscription, error) { + + logs, sub, err := _AcctManager.contract.WatchLogs(opts, "AccountAccessRevoked") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AcctManagerAccountAccessRevoked) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAccountAccessRevoked is a log parse operation binding the contract event 0x6b5105396435a8a139aeed682dd573cd2a7e6279de77f8c11f95a30399212ad1. +// +// Solidity: event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin) +func (_AcctManager *AcctManagerFilterer) ParseAccountAccessRevoked(log types.Log) (*AcctManagerAccountAccessRevoked, error) { + event := new(AcctManagerAccountAccessRevoked) + if err := _AcctManager.contract.UnpackLog(event, "AccountAccessRevoked", log); err != nil { + return nil, err + } + return event, nil +} + +// AcctManagerAccountStatusChangedIterator is returned from FilterAccountStatusChanged and is used to iterate over the raw logs and unpacked data for AccountStatusChanged events raised by the AcctManager contract. +type AcctManagerAccountStatusChangedIterator struct { + Event *AcctManagerAccountStatusChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AcctManagerAccountStatusChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountStatusChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AcctManagerAccountStatusChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AcctManagerAccountStatusChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AcctManagerAccountStatusChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AcctManagerAccountStatusChanged represents a AccountStatusChanged event raised by the AcctManager contract. +type AcctManagerAccountStatusChanged struct { + Account common.Address + OrgId string + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAccountStatusChanged is a free log retrieval operation binding the contract event 0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25. +// +// Solidity: event AccountStatusChanged(address _account, string _orgId, uint256 _status) +func (_AcctManager *AcctManagerFilterer) FilterAccountStatusChanged(opts *bind.FilterOpts) (*AcctManagerAccountStatusChangedIterator, error) { + + logs, sub, err := _AcctManager.contract.FilterLogs(opts, "AccountStatusChanged") + if err != nil { + return nil, err + } + return &AcctManagerAccountStatusChangedIterator{contract: _AcctManager.contract, event: "AccountStatusChanged", logs: logs, sub: sub}, nil +} + +var AccountStatusChangedTopicHash = "0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25" + +// WatchAccountStatusChanged is a free log subscription operation binding the contract event 0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25. +// +// Solidity: event AccountStatusChanged(address _account, string _orgId, uint256 _status) +func (_AcctManager *AcctManagerFilterer) WatchAccountStatusChanged(opts *bind.WatchOpts, sink chan<- *AcctManagerAccountStatusChanged) (event.Subscription, error) { + + logs, sub, err := _AcctManager.contract.WatchLogs(opts, "AccountStatusChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AcctManagerAccountStatusChanged) + if err := _AcctManager.contract.UnpackLog(event, "AccountStatusChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAccountStatusChanged is a log parse operation binding the contract event 0x36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b25. +// +// Solidity: event AccountStatusChanged(address _account, string _orgId, uint256 _status) +func (_AcctManager *AcctManagerFilterer) ParseAccountStatusChanged(log types.Log) (*AcctManagerAccountStatusChanged, error) { + event := new(AcctManagerAccountStatusChanged) + if err := _AcctManager.contract.UnpackLog(event, "AccountStatusChanged", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v2/bind/nodes.go b/permission/v2/bind/nodes.go new file mode 100644 index 0000000000..c9184b295b --- /dev/null +++ b/permission/v2/bind/nodes.go @@ -0,0 +1,1427 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// NodeManagerABI is the input ABI used to generate the binding from. +const NodeManagerABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateNodeStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"enodeId\",\"type\":\"string\"}],\"name\":\"getNodeDetails\",\"outputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_nodeStatus\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addAdminNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"}],\"name\":\"connectionAllowed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addOrgNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_nodeIndex\",\"type\":\"uint256\"}],\"name\":\"getNodeDetailsFromIndex\",\"outputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_nodeStatus\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfNodes\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"approveNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeDeactivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeActivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeBlacklisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeRecoveryInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_enodeId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ip\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_port\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_raftport\",\"type\":\"uint16\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"NodeRecoveryCompleted\",\"type\":\"event\"}]" + +var NodeManagerParsedABI, _ = abi.JSON(strings.NewReader(NodeManagerABI)) + +// NodeManagerBin is the compiled bytecode used for deploying new contracts. +var NodeManagerBin = "0x608060405234801561001057600080fd5b5060405160208061338f8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905561332d806100626000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c80634c573311116100665780634c5733111461042b578063549583df1461073057806397c07a9b146108f2578063b81c806a1461090f578063f82e08ac1461092957610093565b806337d50b27146100985780633f0e0e471461025e5780634530abe11461042b57806345a59e5b146105ed575b600080fd5b61025c600480360360c08110156100ae57600080fd5b810190602081018135600160201b8111156100c857600080fd5b8201836020820111156100da57600080fd5b803590602001918460018302840111600160201b831117156100fb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561014d57600080fd5b82018360208201111561015f57600080fd5b803590602001918460018302840111600160201b8311171561018057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b8111156101e657600080fd5b8201836020820111156101f857600080fd5b803590602001918460018302840111600160201b8311171561021957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250610aeb915050565b005b6102cc6004803603602081101561027457600080fd5b810190602081018135600160201b81111561028e57600080fd5b8201836020820111156102a057600080fd5b803590602001918460018302840111600160201b831117156102c157600080fd5b50909250905061160a565b6040805161ffff80861660608301528416608082015260a0810183905260c080825288519082015287519091829160208084019284019160e08501918c019080838360005b83811015610329578181015183820152602001610311565b50505050905090810190601f1680156103565780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b83811015610389578181015183820152602001610371565b50505050905090810190601f1680156103b65780820380516001836020036101000a031916815260200191505b5084810382528851815288516020918201918a019080838360005b838110156103e95781810151838201526020016103d1565b50505050905090810190601f1680156104165780820380516001836020036101000a031916815260200191505b50995050505050505050505060405180910390f35b61025c600480360360a081101561044157600080fd5b810190602081018135600160201b81111561045b57600080fd5b82018360208201111561046d57600080fd5b803590602001918460018302840111600160201b8311171561048e57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156104e057600080fd5b8201836020820111156104f257600080fd5b803590602001918460018302840111600160201b8311171561051357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b81111561057957600080fd5b82018360208201111561058b57600080fd5b803590602001918460018302840111600160201b831117156105ac57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506119d1945050505050565b61071c6004803603606081101561060357600080fd5b810190602081018135600160201b81111561061d57600080fd5b82018360208201111561062f57600080fd5b803590602001918460018302840111600160201b8311171561065057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156106a257600080fd5b8201836020820111156106b457600080fd5b803590602001918460018302840111600160201b831117156106d557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903561ffff169150611ec79050565b604080519115158252519081900360200190f35b61025c600480360360a081101561074657600080fd5b810190602081018135600160201b81111561076057600080fd5b82018360208201111561077257600080fd5b803590602001918460018302840111600160201b8311171561079357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156107e557600080fd5b8201836020820111156107f757600080fd5b803590602001918460018302840111600160201b8311171561081857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b81111561087e57600080fd5b82018360208201111561089057600080fd5b803590602001918460018302840111600160201b831117156108b157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506121e8945050505050565b6102cc6004803603602081101561090857600080fd5b50356125d5565b610917612879565b60408051918252519081900360200190f35b61025c600480360360a081101561093f57600080fd5b810190602081018135600160201b81111561095957600080fd5b82018360208201111561096b57600080fd5b803590602001918460018302840111600160201b8311171561098c57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156109de57600080fd5b8201836020820111156109f057600080fd5b803590602001918460018302840111600160201b83111715610a1157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b811115610a7757600080fd5b820183602082011115610a8957600080fd5b803590602001918460018302840111600160201b83111715610aaa57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612880945050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610b3857600080fd5b505afa158015610b4c573d6000803e3d6000fd5b505050506040513d6020811015610b6257600080fd5b50516001600160a01b03163314610bb75760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8560036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610bfd578181015183820152602001610be5565b50505050905090810190601f168015610c2a5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020541515610caa5760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b610cb48784612eed565b1515610cf457604051600160e51b62461bcd02815260040180806020018281038252602a815260200180613265602a913960400191505060405180910390fd5b8160011480610d035750816002145b80610d0e5750816003145b80610d195750816004145b80610d245750816005145b1515610d6457604051600160e51b62461bcd0281526004018080602001828103825260268152602001806132af6026913960400191505060405180910390fd5b6000610d6f88613047565b9050866040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610db2578181015183820152602001610d9a565b50505050905090810190601f168015610ddf5780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120600182815481101515610e0957fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610ea65780601f10610e7b57610100808354040283529160200191610ea6565b820191906000526020600020905b815481529060010190602001808311610e8957829003601f168201915b50509250505060405160208183030381529060405280519060200120141580610ef957508561ffff16600182815481101515610ede57fe5b600091825260209091206002600590920201015461ffff1614155b80610f3457508461ffff16600182815481101515610f1357fe5b600091825260209091206005909102016002015462010000900461ffff1614155b15610f3f5750611601565b826001141561114857610f51886130f0565b600214610f965760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b6003600182815481101515610fa757fe5b9060005260206000209060050201600401819055507ff631019be71bc682c59150635d714061185232e98e60de8bdd87bbee239cc5c888888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360005b8381101561104357818101518382015260200161102b565b50505050905090810190601f1680156110705780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b838110156110a357818101518382015260200161108b565b50505050905090810190601f1680156110d05780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156111035781810151838201526020016110eb565b50505050905090810190601f1680156111305780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a16115ff565b826002141561124b5761115a886130f0565b60031461119f5760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b60026001828154811015156111b057fe5b9060005260206000209060050201600401819055507ffb98f62dea866f0c373574c8523f611d0db1d8f19cc1b95d07a221d36a6a45de88888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360008381101561104357818101518382015260200161102b565b826003141561130057600460018281548110151561126557fe5b9060005260206000209060050201600401819055507f25300d4d785e654bc9b7979700cfa0fdc9ace890a46841fecfce661fd2c41a3388888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360008381101561104357818101518382015260200161102b565b826004141561140357611312886130f0565b6004146113575760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b600560018281548110151561136857fe5b9060005260206000209060050201600401819055507f72779f66ea90e28bae76fbfe03eaef5ae01699976c7493f93186ab9560ccfaa488888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360008381101561104357818101518382015260200161102b565b61140c886130f0565b6005146114515760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b600260018281548110151561146257fe5b9060005260206000209060050201600401819055507f60aac8c36efdaabf125dc9ec2124bde8b3ceafe5c8b4fc8063fc4ac9017eb0be88888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360005b838110156114fe5781810151838201526020016114e6565b50505050905090810190601f16801561152b5780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b8381101561155e578181015183820152602001611546565b50505050905090810190601f16801561158b5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156115be5781810151838201526020016115a6565b50505050905090810190601f1680156115eb5780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a15b505b50505050505050565b6060806060600080600060026000866040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611659578181015183820152602001611641565b50505050905090810190601f1680156116865780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054600014156116f157505060408051602080820183526000808352835180830185528181528451928301909452808252919650919450909250905080806119c7565b600061173289898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061304792505050565b905060018181548110151561174357fe5b906000526020600020906005020160030160018281548110151561176357fe5b906000526020600020906005020160000160018381548110151561178357fe5b90600052602060002090600502016001016001848154811015156117a357fe5b60009182526020909120600260059092020101546001805461ffff90921691869081106117cc57fe5b906000526020600020906005020160020160029054906101000a900461ffff166001868154811015156117fb57fe5b600091825260209182902060046005909202010154865460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928891908301828280156118965780601f1061186b57610100808354040283529160200191611896565b820191906000526020600020905b81548152906001019060200180831161187957829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a9450925084019050828280156119245780601f106118f957610100808354040283529160200191611924565b820191906000526020600020905b81548152906001019060200180831161190757829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a50899450925084019050828280156119b25780601f10611987576101008083540402835291602001916119b2565b820191906000526020600020905b81548152906001019060200180831161199557829003601f168201915b50505050509350965096509650965096509650505b9295509295509295565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611a1e57600080fd5b505afa158015611a32573d6000803e3d6000fd5b505050506040513d6020811015611a4857600080fd5b50516001600160a01b03163314611a9d5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8460036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611ae3578181015183820152602001611acb565b50505050905090810190601f168015611b105780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415611b8f5760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b60048054600101908190556040805160208082018181528a519383019390935289516003936000938c9391928392606090920191850190808383895b83811015611be3578181015183820152602001611bcb565b50505050905090810190601f168015611c105780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652858201969096529385016000908120969096555050825160c0810184528a81528083018a905261ffff808a169482019490945292871660608401525060808201859052600260a083015260018054808201808355919094528251805191946005027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60192611cbe928492909101906131cc565b506020828101518051611cd792600185019201906131cc565b506040820151600282018054606085015161ffff908116620100000263ffff0000199190941661ffff19909216919091171691909117905560808201518051611d2a9160038401916020909101906131cc565b5060a082015181600401555050507f9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d86868686866040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360005b83811015611dbf578181015183820152602001611da7565b50505050905090810190601f168015611dec5780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015611e1f578181015183820152602001611e07565b50505050905090810190601f168015611e4c5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015611e7f578181015183820152602001611e67565b50505050905090810190601f168015611eac5780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a1505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611f1657600080fd5b505afa158015611f2a573d6000803e3d6000fd5b505050506040513d6020811015611f4057600080fd5b50516001600160a01b03163314611f955760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60036000856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611fda578181015183820152602001611fc2565b50505050905090810190601f1680156120075780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012081526020019081526020016000205460001415612041575060006121e1565b600061204c85613047565b905060018181548110151561205d57fe5b90600052602060002090600502016004015460021480156121cc5750836040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156120ba5781810151838201526020016120a2565b50505050905090810190601f1680156120e75780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561211157fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156121ae5780601f10612183576101008083540402835291602001916121ae565b820191906000526020600020905b81548152906001019060200180831161219157829003601f168201915b50509250505060405160208183030381529060405280519060200120145b156121db5760019150506121e1565b60009150505b9392505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561223557600080fd5b505afa158015612249573d6000803e3d6000fd5b505050506040513d602081101561225f57600080fd5b50516001600160a01b031633146122b45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8460036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156122fa5781810151838201526020016122e2565b50505050905090810190601f1680156123275780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652908501959095525050500160002054156123a65760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b60048054600101908190556040805160208082018181528a519383019390935289516003936000938c9391928392606090920191850190808383895b838110156123fa5781810151838201526020016123e2565b50505050905090810190601f1680156124275780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652858201969096529385016000908120969096555050825160c0810184528a81528083018a905261ffff808a169482019490945292871660608401525060808201859052600160a083018190528054808201808355919094528251805191946005027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601926124d5928492909101906131cc565b5060208281015180516124ee92600185019201906131cc565b506040820151600282018054606085015161ffff908116620100000263ffff0000199190941661ffff199092169190911716919091179055608082015180516125419160038401916020909101906131cc565b5060a082015181600401555050507ff9bad9f8a2dccc52fad61273a7fd673335b420319506c19b87df9ce7a19732da86868686866040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff168152602001806020018481038452898181518152602001915080519060200190808383600083811015611dbf578181015183820152602001611da7565b606080606060008060006001878154811015156125ee57fe5b906000526020600020906005020160030160018881548110151561260e57fe5b906000526020600020906005020160000160018981548110151561262e57fe5b906000526020600020906005020160010160018a81548110151561264e57fe5b60009182526020909120600260059092020101546001805461ffff909216918c90811061267757fe5b906000526020600020906005020160020160029054906101000a900461ffff1660018c8154811015156126a657fe5b600091825260209182902060046005909202010154865460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928891908301828280156127415780601f1061271657610100808354040283529160200191612741565b820191906000526020600020905b81548152906001019060200180831161272457829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a9450925084019050828280156127cf5780601f106127a4576101008083540402835291602001916127cf565b820191906000526020600020905b8154815290600101906020018083116127b257829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a508994509250840190508282801561285d5780601f106128325761010080835404028352916020019161285d565b820191906000526020600020905b81548152906001019060200180831161284057829003601f168201915b5050505050935095509550955095509550955091939550919395565b6004545b90565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156128cd57600080fd5b505afa1580156128e1573d6000803e3d6000fd5b505050506040513d60208110156128f757600080fd5b50516001600160a01b0316331461294c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8460036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561299257818101518382015260200161297a565b50505050905090810190601f1680156129bf5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020541515612a3f5760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b612a498683612eed565b1515612a8957604051600160e51b62461bcd02815260040180806020018281038252602d8152602001806132d5602d913960400191505060405180910390fd5b612a92866130f0565b600114612ae95760408051600160e51b62461bcd02815260206004820152601c60248201527f6e6f7468696e672070656e64696e6720666f7220617070726f76616c00000000604482015290519081900360640190fd5b6000612af487613047565b9050856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612b37578181015183820152602001612b1f565b50505050905090810190601f168015612b645780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120600182815481101515612b8e57fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015612c2b5780601f10612c0057610100808354040283529160200191612c2b565b820191906000526020600020905b815481529060010190602001808311612c0e57829003601f168201915b50509250505060405160208183030381529060405280519060200120141580612c7e57508461ffff16600182815481101515612c6357fe5b600091825260209091206002600590920201015461ffff1614155b80612cb957508361ffff16600182815481101515612c9857fe5b600091825260209091206005909102016002015462010000900461ffff1614155b15612cc45750612ee5565b6002600182815481101515612cd557fe5b9060005260206000209060050201600401819055507f9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d600182815481101515612d1a57fe5b9060005260206000209060050201600001878787600186815481101515612d3d57fe5b90600052602060002090600502016003016040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015612dfb5780601f10612dd057610100808354040283529160200191612dfb565b820191906000526020600020905b815481529060010190602001808311612dde57829003601f168201915b505084810383528851815288516020918201918a019080838360005b83811015612e2f578181015183820152602001612e17565b50505050905090810190601f168015612e5c5780820380516001836020036101000a031916815260200191505b50848103825285546002600019610100600184161502019091160480825260209091019086908015612ecf5780601f10612ea457610100808354040283529160200191612ecf565b820191906000526020600020905b815481529060010190602001808311612eb257829003601f168201915b50509850505050505050505060405180910390a1505b505050505050565b6000816040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612f30578181015183820152602001612f18565b50505050905090810190601f168015612f5d5780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001206001612f8385613047565b81548110612f8d57fe5b60009182526020918290206040805180850194855260036005909402909201929092018054600260001961010060018416150201909116049282018390529291829160600190849080156130225780601f10612ff757610100808354040283529160200191613022565b820191906000526020600020905b81548152906001019060200180831161300557829003601f168201915b5050925050506040516020818303038152906040528051906020012014905092915050565b6000600160036000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015613090578181015183820152602001613078565b50505050905090810190601f1680156130bd5780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001208152602001908152602001600020540390505b919050565b600060036000836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561313757818101518382015260200161311f565b50505050905090810190601f1680156131645780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001208152602001908152602001600020546000141561319e575060006130eb565b60016131a983613047565b815481106131b357fe5b9060005260206000209060050201600401549050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061320d57805160ff191683800117855561323a565b8280016001018555821561323a579182015b8281111561323a57825182559160200191906001019061321f565b5061324692915061324a565b5090565b61287d91905b80821115613246576000815560010161325056fe656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f72676f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000696e76616c6964206f7065726174696f6e2e2077726f6e6720616374696f6e20706173736564656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f7267206964a165627a7a7230582033fd8af5439a9af79764088da6847651cc33d3b41698fff5d54b3e47419c5c6a0029" + +// DeployNodeManager deploys a new Ethereum contract, binding an instance of NodeManager to it. +func DeployNodeManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *NodeManager, error) { + parsed, err := abi.JSON(strings.NewReader(NodeManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(NodeManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &NodeManager{NodeManagerCaller: NodeManagerCaller{contract: contract}, NodeManagerTransactor: NodeManagerTransactor{contract: contract}, NodeManagerFilterer: NodeManagerFilterer{contract: contract}}, nil +} + +// NodeManager is an auto generated Go binding around an Ethereum contract. +type NodeManager struct { + NodeManagerCaller // Read-only binding to the contract + NodeManagerTransactor // Write-only binding to the contract + NodeManagerFilterer // Log filterer for contract events +} + +// NodeManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type NodeManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NodeManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type NodeManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NodeManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type NodeManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NodeManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type NodeManagerSession struct { + Contract *NodeManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NodeManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type NodeManagerCallerSession struct { + Contract *NodeManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// NodeManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type NodeManagerTransactorSession struct { + Contract *NodeManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NodeManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type NodeManagerRaw struct { + Contract *NodeManager // Generic contract binding to access the raw methods on +} + +// NodeManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type NodeManagerCallerRaw struct { + Contract *NodeManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// NodeManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type NodeManagerTransactorRaw struct { + Contract *NodeManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewNodeManager creates a new instance of NodeManager, bound to a specific deployed contract. +func NewNodeManager(address common.Address, backend bind.ContractBackend) (*NodeManager, error) { + contract, err := bindNodeManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &NodeManager{NodeManagerCaller: NodeManagerCaller{contract: contract}, NodeManagerTransactor: NodeManagerTransactor{contract: contract}, NodeManagerFilterer: NodeManagerFilterer{contract: contract}}, nil +} + +// NewNodeManagerCaller creates a new read-only instance of NodeManager, bound to a specific deployed contract. +func NewNodeManagerCaller(address common.Address, caller bind.ContractCaller) (*NodeManagerCaller, error) { + contract, err := bindNodeManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &NodeManagerCaller{contract: contract}, nil +} + +// NewNodeManagerTransactor creates a new write-only instance of NodeManager, bound to a specific deployed contract. +func NewNodeManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*NodeManagerTransactor, error) { + contract, err := bindNodeManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &NodeManagerTransactor{contract: contract}, nil +} + +// NewNodeManagerFilterer creates a new log filterer instance of NodeManager, bound to a specific deployed contract. +func NewNodeManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*NodeManagerFilterer, error) { + contract, err := bindNodeManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &NodeManagerFilterer{contract: contract}, nil +} + +// bindNodeManager binds a generic wrapper to an already deployed contract. +func bindNodeManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(NodeManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_NodeManager *NodeManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _NodeManager.Contract.NodeManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_NodeManager *NodeManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NodeManager.Contract.NodeManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_NodeManager *NodeManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NodeManager.Contract.NodeManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_NodeManager *NodeManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _NodeManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_NodeManager *NodeManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NodeManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_NodeManager *NodeManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NodeManager.Contract.contract.Transact(opts, method, params...) +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_NodeManager *NodeManagerCaller) ConnectionAllowed(opts *bind.CallOpts, _enodeId string, _ip string, _port uint16) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _NodeManager.contract.Call(opts, out, "connectionAllowed", _enodeId, _ip, _port) + return *ret0, err +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_NodeManager *NodeManagerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { + return _NodeManager.Contract.ConnectionAllowed(&_NodeManager.CallOpts, _enodeId, _ip, _port) +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_NodeManager *NodeManagerCallerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { + return _NodeManager.Contract.ConnectionAllowed(&_NodeManager.CallOpts, _enodeId, _ip, _port) +} + +// GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. +// +// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCaller) GetNodeDetails(opts *bind.CallOpts, enodeId string) (struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int +}, error) { + ret := new(struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int + }) + out := ret + err := _NodeManager.contract.Call(opts, out, "getNodeDetails", enodeId) + return *ret, err +} + +// GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. +// +// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +func (_NodeManager *NodeManagerSession) GetNodeDetails(enodeId string) (struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetails(&_NodeManager.CallOpts, enodeId) +} + +// GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. +// +// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCallerSession) GetNodeDetails(enodeId string) (struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetails(&_NodeManager.CallOpts, enodeId) +} + +// GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. +// +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCaller) GetNodeDetailsFromIndex(opts *bind.CallOpts, _nodeIndex *big.Int) (struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int +}, error) { + ret := new(struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int + }) + out := ret + err := _NodeManager.contract.Call(opts, out, "getNodeDetailsFromIndex", _nodeIndex) + return *ret, err +} + +// GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. +// +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +func (_NodeManager *NodeManagerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetailsFromIndex(&_NodeManager.CallOpts, _nodeIndex) +} + +// GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. +// +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +func (_NodeManager *NodeManagerCallerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { + OrgId string + EnodeId string + Ip string + Port uint16 + Raftport uint16 + NodeStatus *big.Int +}, error) { + return _NodeManager.Contract.GetNodeDetailsFromIndex(&_NodeManager.CallOpts, _nodeIndex) +} + +// GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. +// +// Solidity: function getNumberOfNodes() constant returns(uint256) +func (_NodeManager *NodeManagerCaller) GetNumberOfNodes(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _NodeManager.contract.Call(opts, out, "getNumberOfNodes") + return *ret0, err +} + +// GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. +// +// Solidity: function getNumberOfNodes() constant returns(uint256) +func (_NodeManager *NodeManagerSession) GetNumberOfNodes() (*big.Int, error) { + return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) +} + +// GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. +// +// Solidity: function getNumberOfNodes() constant returns(uint256) +func (_NodeManager *NodeManagerCallerSession) GetNumberOfNodes() (*big.Int, error) { + return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x4530abe1. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) AddAdminNode(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "addAdminNode", _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x4530abe1. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerSession) AddAdminNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddAdminNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x4530abe1. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) AddAdminNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddAdminNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddNode is a paid mutator transaction binding the contract method 0x549583df. +// +// Solidity: function addNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) AddNode(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "addNode", _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddNode is a paid mutator transaction binding the contract method 0x549583df. +// +// Solidity: function addNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerSession) AddNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddNode is a paid mutator transaction binding the contract method 0x549583df. +// +// Solidity: function addNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) AddNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddOrgNode is a paid mutator transaction binding the contract method 0x4c573311. +// +// Solidity: function addOrgNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) AddOrgNode(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "addOrgNode", _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddOrgNode is a paid mutator transaction binding the contract method 0x4c573311. +// +// Solidity: function addOrgNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerSession) AddOrgNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddOrgNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// AddOrgNode is a paid mutator transaction binding the contract method 0x4c573311. +// +// Solidity: function addOrgNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) AddOrgNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.AddOrgNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// ApproveNode is a paid mutator transaction binding the contract method 0xf82e08ac. +// +// Solidity: function approveNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactor) ApproveNode(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "approveNode", _enodeId, _ip, _port, _raftport, _orgId) +} + +// ApproveNode is a paid mutator transaction binding the contract method 0xf82e08ac. +// +// Solidity: function approveNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerSession) ApproveNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.ApproveNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// ApproveNode is a paid mutator transaction binding the contract method 0xf82e08ac. +// +// Solidity: function approveNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) returns() +func (_NodeManager *NodeManagerTransactorSession) ApproveNode(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string) (*types.Transaction, error) { + return _NodeManager.Contract.ApproveNode(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x37d50b27. +// +// Solidity: function updateNodeStatus(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId, uint256 _action) returns() +func (_NodeManager *NodeManagerTransactor) UpdateNodeStatus(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _NodeManager.contract.Transact(opts, "updateNodeStatus", _enodeId, _ip, _port, _raftport, _orgId, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x37d50b27. +// +// Solidity: function updateNodeStatus(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId, uint256 _action) returns() +func (_NodeManager *NodeManagerSession) UpdateNodeStatus(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _NodeManager.Contract.UpdateNodeStatus(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x37d50b27. +// +// Solidity: function updateNodeStatus(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId, uint256 _action) returns() +func (_NodeManager *NodeManagerTransactorSession) UpdateNodeStatus(_enodeId string, _ip string, _port uint16, _raftport uint16, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _NodeManager.Contract.UpdateNodeStatus(&_NodeManager.TransactOpts, _enodeId, _ip, _port, _raftport, _orgId, _action) +} + +// NodeManagerNodeActivatedIterator is returned from FilterNodeActivated and is used to iterate over the raw logs and unpacked data for NodeActivated events raised by the NodeManager contract. +type NodeManagerNodeActivatedIterator struct { + Event *NodeManagerNodeActivated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeActivatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeActivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeActivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeActivatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeActivatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeActivated represents a NodeActivated event raised by the NodeManager contract. +type NodeManagerNodeActivated struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeActivated is a free log retrieval operation binding the contract event 0xfb98f62dea866f0c373574c8523f611d0db1d8f19cc1b95d07a221d36a6a45de. +// +// Solidity: event NodeActivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeActivated(opts *bind.FilterOpts) (*NodeManagerNodeActivatedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeActivated") + if err != nil { + return nil, err + } + return &NodeManagerNodeActivatedIterator{contract: _NodeManager.contract, event: "NodeActivated", logs: logs, sub: sub}, nil +} + +var NodeActivatedTopicHash = "0xfb98f62dea866f0c373574c8523f611d0db1d8f19cc1b95d07a221d36a6a45de" + +// WatchNodeActivated is a free log subscription operation binding the contract event 0xfb98f62dea866f0c373574c8523f611d0db1d8f19cc1b95d07a221d36a6a45de. +// +// Solidity: event NodeActivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeActivated(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeActivated) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeActivated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeActivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeActivated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeActivated is a log parse operation binding the contract event 0xfb98f62dea866f0c373574c8523f611d0db1d8f19cc1b95d07a221d36a6a45de. +// +// Solidity: event NodeActivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeActivated(log types.Log) (*NodeManagerNodeActivated, error) { + event := new(NodeManagerNodeActivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeActivated", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeApprovedIterator is returned from FilterNodeApproved and is used to iterate over the raw logs and unpacked data for NodeApproved events raised by the NodeManager contract. +type NodeManagerNodeApprovedIterator struct { + Event *NodeManagerNodeApproved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeApprovedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeApprovedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeApprovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeApproved represents a NodeApproved event raised by the NodeManager contract. +type NodeManagerNodeApproved struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeApproved is a free log retrieval operation binding the contract event 0x9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d. +// +// Solidity: event NodeApproved(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeApproved(opts *bind.FilterOpts) (*NodeManagerNodeApprovedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeApproved") + if err != nil { + return nil, err + } + return &NodeManagerNodeApprovedIterator{contract: _NodeManager.contract, event: "NodeApproved", logs: logs, sub: sub}, nil +} + +var NodeApprovedTopicHash = "0x9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d" + +// WatchNodeApproved is a free log subscription operation binding the contract event 0x9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d. +// +// Solidity: event NodeApproved(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeApproved(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeApproved) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeApproved") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeApproved) + if err := _NodeManager.contract.UnpackLog(event, "NodeApproved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeApproved is a log parse operation binding the contract event 0x9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d. +// +// Solidity: event NodeApproved(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeApproved(log types.Log) (*NodeManagerNodeApproved, error) { + event := new(NodeManagerNodeApproved) + if err := _NodeManager.contract.UnpackLog(event, "NodeApproved", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeBlacklistedIterator is returned from FilterNodeBlacklisted and is used to iterate over the raw logs and unpacked data for NodeBlacklisted events raised by the NodeManager contract. +type NodeManagerNodeBlacklistedIterator struct { + Event *NodeManagerNodeBlacklisted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeBlacklistedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeBlacklisted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeBlacklisted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeBlacklistedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeBlacklistedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeBlacklisted represents a NodeBlacklisted event raised by the NodeManager contract. +type NodeManagerNodeBlacklisted struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeBlacklisted is a free log retrieval operation binding the contract event 0x25300d4d785e654bc9b7979700cfa0fdc9ace890a46841fecfce661fd2c41a33. +// +// Solidity: event NodeBlacklisted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeBlacklisted(opts *bind.FilterOpts) (*NodeManagerNodeBlacklistedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeBlacklisted") + if err != nil { + return nil, err + } + return &NodeManagerNodeBlacklistedIterator{contract: _NodeManager.contract, event: "NodeBlacklisted", logs: logs, sub: sub}, nil +} + +var NodeBlacklistedTopicHash = "0x25300d4d785e654bc9b7979700cfa0fdc9ace890a46841fecfce661fd2c41a33" + +// WatchNodeBlacklisted is a free log subscription operation binding the contract event 0x25300d4d785e654bc9b7979700cfa0fdc9ace890a46841fecfce661fd2c41a33. +// +// Solidity: event NodeBlacklisted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeBlacklisted(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeBlacklisted) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeBlacklisted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeBlacklisted) + if err := _NodeManager.contract.UnpackLog(event, "NodeBlacklisted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeBlacklisted is a log parse operation binding the contract event 0x25300d4d785e654bc9b7979700cfa0fdc9ace890a46841fecfce661fd2c41a33. +// +// Solidity: event NodeBlacklisted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeBlacklisted(log types.Log) (*NodeManagerNodeBlacklisted, error) { + event := new(NodeManagerNodeBlacklisted) + if err := _NodeManager.contract.UnpackLog(event, "NodeBlacklisted", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeDeactivatedIterator is returned from FilterNodeDeactivated and is used to iterate over the raw logs and unpacked data for NodeDeactivated events raised by the NodeManager contract. +type NodeManagerNodeDeactivatedIterator struct { + Event *NodeManagerNodeDeactivated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeDeactivatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeDeactivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeDeactivated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeDeactivatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeDeactivatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeDeactivated represents a NodeDeactivated event raised by the NodeManager contract. +type NodeManagerNodeDeactivated struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeDeactivated is a free log retrieval operation binding the contract event 0xf631019be71bc682c59150635d714061185232e98e60de8bdd87bbee239cc5c8. +// +// Solidity: event NodeDeactivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeDeactivated(opts *bind.FilterOpts) (*NodeManagerNodeDeactivatedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeDeactivated") + if err != nil { + return nil, err + } + return &NodeManagerNodeDeactivatedIterator{contract: _NodeManager.contract, event: "NodeDeactivated", logs: logs, sub: sub}, nil +} + +var NodeDeactivatedTopicHash = "0xf631019be71bc682c59150635d714061185232e98e60de8bdd87bbee239cc5c8" + +// WatchNodeDeactivated is a free log subscription operation binding the contract event 0xf631019be71bc682c59150635d714061185232e98e60de8bdd87bbee239cc5c8. +// +// Solidity: event NodeDeactivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeDeactivated(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeDeactivated) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeDeactivated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeDeactivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeDeactivated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeDeactivated is a log parse operation binding the contract event 0xf631019be71bc682c59150635d714061185232e98e60de8bdd87bbee239cc5c8. +// +// Solidity: event NodeDeactivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeDeactivated(log types.Log) (*NodeManagerNodeDeactivated, error) { + event := new(NodeManagerNodeDeactivated) + if err := _NodeManager.contract.UnpackLog(event, "NodeDeactivated", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeProposedIterator is returned from FilterNodeProposed and is used to iterate over the raw logs and unpacked data for NodeProposed events raised by the NodeManager contract. +type NodeManagerNodeProposedIterator struct { + Event *NodeManagerNodeProposed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeProposedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeProposedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeProposedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeProposed represents a NodeProposed event raised by the NodeManager contract. +type NodeManagerNodeProposed struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeProposed is a free log retrieval operation binding the contract event 0xf9bad9f8a2dccc52fad61273a7fd673335b420319506c19b87df9ce7a19732da. +// +// Solidity: event NodeProposed(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeProposed(opts *bind.FilterOpts) (*NodeManagerNodeProposedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeProposed") + if err != nil { + return nil, err + } + return &NodeManagerNodeProposedIterator{contract: _NodeManager.contract, event: "NodeProposed", logs: logs, sub: sub}, nil +} + +var NodeProposedTopicHash = "0xf9bad9f8a2dccc52fad61273a7fd673335b420319506c19b87df9ce7a19732da" + +// WatchNodeProposed is a free log subscription operation binding the contract event 0xf9bad9f8a2dccc52fad61273a7fd673335b420319506c19b87df9ce7a19732da. +// +// Solidity: event NodeProposed(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeProposed(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeProposed) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeProposed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeProposed) + if err := _NodeManager.contract.UnpackLog(event, "NodeProposed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeProposed is a log parse operation binding the contract event 0xf9bad9f8a2dccc52fad61273a7fd673335b420319506c19b87df9ce7a19732da. +// +// Solidity: event NodeProposed(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeProposed(log types.Log) (*NodeManagerNodeProposed, error) { + event := new(NodeManagerNodeProposed) + if err := _NodeManager.contract.UnpackLog(event, "NodeProposed", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeRecoveryCompletedIterator is returned from FilterNodeRecoveryCompleted and is used to iterate over the raw logs and unpacked data for NodeRecoveryCompleted events raised by the NodeManager contract. +type NodeManagerNodeRecoveryCompletedIterator struct { + Event *NodeManagerNodeRecoveryCompleted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeRecoveryCompletedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryCompleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryCompleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeRecoveryCompletedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeRecoveryCompletedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeRecoveryCompleted represents a NodeRecoveryCompleted event raised by the NodeManager contract. +type NodeManagerNodeRecoveryCompleted struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeRecoveryCompleted is a free log retrieval operation binding the contract event 0x60aac8c36efdaabf125dc9ec2124bde8b3ceafe5c8b4fc8063fc4ac9017eb0be. +// +// Solidity: event NodeRecoveryCompleted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeRecoveryCompleted(opts *bind.FilterOpts) (*NodeManagerNodeRecoveryCompletedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeRecoveryCompleted") + if err != nil { + return nil, err + } + return &NodeManagerNodeRecoveryCompletedIterator{contract: _NodeManager.contract, event: "NodeRecoveryCompleted", logs: logs, sub: sub}, nil +} + +var NodeRecoveryCompletedTopicHash = "0x60aac8c36efdaabf125dc9ec2124bde8b3ceafe5c8b4fc8063fc4ac9017eb0be" + +// WatchNodeRecoveryCompleted is a free log subscription operation binding the contract event 0x60aac8c36efdaabf125dc9ec2124bde8b3ceafe5c8b4fc8063fc4ac9017eb0be. +// +// Solidity: event NodeRecoveryCompleted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeRecoveryCompleted(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeRecoveryCompleted) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeRecoveryCompleted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeRecoveryCompleted) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryCompleted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeRecoveryCompleted is a log parse operation binding the contract event 0x60aac8c36efdaabf125dc9ec2124bde8b3ceafe5c8b4fc8063fc4ac9017eb0be. +// +// Solidity: event NodeRecoveryCompleted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeRecoveryCompleted(log types.Log) (*NodeManagerNodeRecoveryCompleted, error) { + event := new(NodeManagerNodeRecoveryCompleted) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryCompleted", log); err != nil { + return nil, err + } + return event, nil +} + +// NodeManagerNodeRecoveryInitiatedIterator is returned from FilterNodeRecoveryInitiated and is used to iterate over the raw logs and unpacked data for NodeRecoveryInitiated events raised by the NodeManager contract. +type NodeManagerNodeRecoveryInitiatedIterator struct { + Event *NodeManagerNodeRecoveryInitiated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NodeManagerNodeRecoveryInitiatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NodeManagerNodeRecoveryInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NodeManagerNodeRecoveryInitiatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NodeManagerNodeRecoveryInitiatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NodeManagerNodeRecoveryInitiated represents a NodeRecoveryInitiated event raised by the NodeManager contract. +type NodeManagerNodeRecoveryInitiated struct { + EnodeId string + Ip string + Port uint16 + Raftport uint16 + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeRecoveryInitiated is a free log retrieval operation binding the contract event 0x72779f66ea90e28bae76fbfe03eaef5ae01699976c7493f93186ab9560ccfaa4. +// +// Solidity: event NodeRecoveryInitiated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) FilterNodeRecoveryInitiated(opts *bind.FilterOpts) (*NodeManagerNodeRecoveryInitiatedIterator, error) { + + logs, sub, err := _NodeManager.contract.FilterLogs(opts, "NodeRecoveryInitiated") + if err != nil { + return nil, err + } + return &NodeManagerNodeRecoveryInitiatedIterator{contract: _NodeManager.contract, event: "NodeRecoveryInitiated", logs: logs, sub: sub}, nil +} + +var NodeRecoveryInitiatedTopicHash = "0x72779f66ea90e28bae76fbfe03eaef5ae01699976c7493f93186ab9560ccfaa4" + +// WatchNodeRecoveryInitiated is a free log subscription operation binding the contract event 0x72779f66ea90e28bae76fbfe03eaef5ae01699976c7493f93186ab9560ccfaa4. +// +// Solidity: event NodeRecoveryInitiated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) WatchNodeRecoveryInitiated(opts *bind.WatchOpts, sink chan<- *NodeManagerNodeRecoveryInitiated) (event.Subscription, error) { + + logs, sub, err := _NodeManager.contract.WatchLogs(opts, "NodeRecoveryInitiated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NodeManagerNodeRecoveryInitiated) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryInitiated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNodeRecoveryInitiated is a log parse operation binding the contract event 0x72779f66ea90e28bae76fbfe03eaef5ae01699976c7493f93186ab9560ccfaa4. +// +// Solidity: event NodeRecoveryInitiated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId) +func (_NodeManager *NodeManagerFilterer) ParseNodeRecoveryInitiated(log types.Log) (*NodeManagerNodeRecoveryInitiated, error) { + event := new(NodeManagerNodeRecoveryInitiated) + if err := _NodeManager.contract.UnpackLog(event, "NodeRecoveryInitiated", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v2/bind/org.go b/permission/v2/bind/org.go new file mode 100644 index 0000000000..7c245ec6a1 --- /dev/null +++ b/permission/v2/bind/org.go @@ -0,0 +1,1101 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// OrgManagerABI is the input ABI used to generate the binding from. +const OrgManagerABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateOrg\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"approveOrgStatusUpdate\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getUltimateParent\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_pOrgId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addSubOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"checkOrgActive\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgIndex\",\"type\":\"uint256\"}],\"name\":\"getOrgInfo\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getSubOrgIndexes\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfOrgs\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_orgStatus\",\"type\":\"uint256\"}],\"name\":\"checkOrgStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_breadth\",\"type\":\"uint256\"},{\"name\":\"_depth\",\"type\":\"uint256\"}],\"name\":\"setUpOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"approveOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getOrgDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"addOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"checkOrgExists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"OrgApproved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_status\",\"type\":\"uint256\"}],\"name\":\"OrgPendingApproval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"}],\"name\":\"OrgSuspended\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_porgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_ultParent\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_level\",\"type\":\"uint256\"}],\"name\":\"OrgSuspensionRevoked\",\"type\":\"event\"}]" + +var OrgManagerParsedABI, _ = abi.JSON(strings.NewReader(OrgManagerABI)) + +// OrgManagerBin is the compiled bytecode used for deploying new contracts. +var OrgManagerBin = "0x608060405260018054600160a01b60ff021916905560046002819055600355600060065534801561002f57600080fd5b50604051602080613bb58339810180604052602081101561004f57600080fd5b5051600180546001600160a01b0319166001600160a01b03909216919091179055613b368061007f6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80637755ebdd1161008c578063e302831611610066578063e302831614610787578063f4d6d9f5146107f5578063f9953de514610863578063ffe40d1d146108d1576100ea565b80637755ebdd146106655780638c8642df1461066d5780639e58eb9f14610713576100ea565b80631f953480116100c85780631f953480146102c25780633fd62ae7146103805780635c4f32ee146104385780635e99f6e5146105a7576100ea565b80630cc27493146100ef57806314f775f91461016f578063177c8d8a146101df575b600080fd5b61015d6004803603604081101561010557600080fd5b810190602081018135600160201b81111561011f57600080fd5b82018360208201111561013157600080fd5b803590602001918460018302840111600160201b8311171561015257600080fd5b919350915035610975565b60408051918252519081900360200190f35b6101dd6004803603604081101561018557600080fd5b810190602081018135600160201b81111561019f57600080fd5b8201836020820111156101b157600080fd5b803590602001918460018302840111600160201b831117156101d257600080fd5b919350915035610d07565b005b61024d600480360360208110156101f557600080fd5b810190602081018135600160201b81111561020f57600080fd5b82018360208201111561022157600080fd5b803590602001918460018302840111600160201b8311171561024257600080fd5b509092509050610ef9565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561028757818101518382015260200161026f565b50505050905090810190601f1680156102b45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101dd600480360360408110156102d857600080fd5b810190602081018135600160201b8111156102f257600080fd5b82018360208201111561030457600080fd5b803590602001918460018302840111600160201b8311171561032557600080fd5b919390929091602081019035600160201b81111561034257600080fd5b82018360208201111561035457600080fd5b803590602001918460018302840111600160201b8311171561037557600080fd5b5090925090506110ae565b6104246004803603602081101561039657600080fd5b810190602081018135600160201b8111156103b057600080fd5b8201836020820111156103c257600080fd5b803590602001918460018302840111600160201b831117156103e357600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061128c945050505050565b604080519115158252519081900360200190f35b6104556004803603602081101561044e57600080fd5b5035611499565b60405180806020018060200180602001868152602001858152602001848103845289818151815260200191508051906020019080838360005b838110156104a657818101518382015260200161048e565b50505050905090810190601f1680156104d35780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b838110156105065781810151838201526020016104ee565b50505050905090810190601f1680156105335780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b8381101561056657818101518382015260200161054e565b50505050905090810190601f1680156105935780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610615600480360360208110156105bd57600080fd5b810190602081018135600160201b8111156105d757600080fd5b8201836020820111156105e957600080fd5b803590602001918460018302840111600160201b8311171561060a57600080fd5b50909250905061170e565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610651578181015183820152602001610639565b505050509050019250505060405180910390f35b61015d61185b565b6104246004803603604081101561068357600080fd5b810190602081018135600160201b81111561069d57600080fd5b8201836020820111156106af57600080fd5b803590602001918460018302840111600160201b831117156106d057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250611862915050565b6101dd6004803603606081101561072957600080fd5b810190602081018135600160201b81111561074357600080fd5b82018360208201111561075557600080fd5b803590602001918460018302840111600160201b8311171561077657600080fd5b9193509150803590602001356119ba565b6101dd6004803603602081101561079d57600080fd5b810190602081018135600160201b8111156107b757600080fd5b8201836020820111156107c957600080fd5b803590602001918460018302840111600160201b831117156107ea57600080fd5b509092509050611ae6565b6104556004803603602081101561080b57600080fd5b810190602081018135600160201b81111561082557600080fd5b82018360208201111561083757600080fd5b803590602001918460018302840111600160201b8311171561085857600080fd5b509092509050611ef1565b6101dd6004803603602081101561087957600080fd5b810190602081018135600160201b81111561089357600080fd5b8201836020820111156108a557600080fd5b803590602001918460018302840111600160201b831117156108c657600080fd5b50909250905061225a565b610424600480360360208110156108e757600080fd5b810190602081018135600160201b81111561090157600080fd5b82018360208201111561091357600080fd5b803590602001918460018302840111600160201b8311171561093457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612407945050505050565b60015460408051600160e41b62e32cf902815290516000926001600160a01b031691630e32cf90916004808301926020929190829003018186803b1580156109bc57600080fd5b505afa1580156109d0573d6000803e3d6000fd5b505050506040513d60208110156109e657600080fd5b50516001600160a01b03163314610a3b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610a7d92508391506124079050565b1515600114610ace5760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8260011480610add5750826002145b1515610b1d57604051600160e51b62461bcd028152600401808060200182810382526025815260200180613a646025913960400191505060405180910390fd5b6000610b5e86868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b9050600481815481101515610b6f57fe5b9060005260206000209060080201600601546001141515610bc457604051600160e51b62461bcd028152600401808060200182810382526027815260200180613a896027913960400191505060405180910390fd5b6000808560011415610bdb57506002905080610bec565b8560021415610bec57506004905060035b610c2d88888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250869250611862915050565b1515600114610c7057604051600160e51b62461bcd028152600401808060200182810382526027815260200180613ab06027913960400191505060405180910390fd5b8560011415610cbd57610cb888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061252292505050565b610cfc565b610cfc88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506127e492505050565b979650505050505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610d5557600080fd5b505afa158015610d69573d6000803e3d6000fd5b505050506040513d6020811015610d7f57600080fd5b50516001600160a01b03163314610dd45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610e1692508391506124079050565b1515600114610e675760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8160011415610eb457610eaf84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061299192505050565b610ef3565b610ef384848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612c4f92505050565b50505050565b60015460408051600160e41b62e32cf902815290516060926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610f4057600080fd5b505afa158015610f54573d6000803e3d6000fd5b505050506040513d6020811015610f6a57600080fd5b50516001600160a01b03163314610fbf5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600461100084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b8154811061100a57fe5b6000918252602091829020600460089092020101805460408051601f60026000196101006001871615020190941693909304928301859004850281018501909152818152928301828280156110a05780601f10611075576101008083540402835291602001916110a0565b820191906000526020600020905b81548152906001019060200180831161108357829003601f168201915b505050505090505b92915050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156110fc57600080fd5b505afa158015611110573d6000803e3d6000fd5b505050506040513d602081101561112657600080fd5b50516001600160a01b0316331461117b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8383838360405160200180858580828437600160f91b601702920191825250600101838380828437808301925050509450505050506040516020818303038152906040526111c881612407565b1561120d5760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b61128585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525060029250829150612d0e9050565b5050505050565b600060056000836040516020018082805190602001908083835b602083106112c55780518252601f1990920191602091820191016112a6565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014151561149057600061132383612495565b905060048181548110151561133457fe5b906000526020600020906008020160010154600214806113735750600480548290811061135d57fe5b9060005260206000209060080201600101546003145b1561148e57600061142c60048381548110151561138c57fe5b6000918252602091829020600460089092020101805460408051601f60026000196101006001871615020190941693909304928301859004850281018501909152818152928301828280156114225780601f106113f757610100808354040283529160200191611422565b820191906000526020600020905b81548152906001019060200180831161140557829003601f168201915b5050505050612495565b905060048181548110151561143d57fe5b9060005260206000209060080201600101546002148061147c5750600480548290811061146657fe5b9060005260206000209060080201600101546003145b1561148c57600192505050611494565b505b505b5060005b919050565b60608060606000806004868154811015156114b057fe5b90600052602060002090600802016000016004878154811015156114d057fe5b90600052602060002090600802016002016004888154811015156114f057fe5b906000526020600020906008020160040160048981548110151561151057fe5b90600052602060002090600802016006015460048a81548110151561153157fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156115d85780601f106115ad576101008083540402835291602001916115d8565b820191906000526020600020905b8154815290600101906020018083116115bb57829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a50899450925084019050828280156116665780601f1061163b57610100808354040283529160200191611666565b820191906000526020600020905b81548152906001019060200180831161164957829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156116f45780601f106116c9576101008083540402835291602001916116f4565b820191906000526020600020905b8154815290600101906020018083116116d757829003601f168201915b505050505092509450945094509450945091939590929450565b606061174f83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061240792505050565b15156001146117a05760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b60006117e184848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b90506004818154811015156117f257fe5b906000526020600020906008020160070180548060200260200160405190810160405280929190818152602001828054801561184d57602002820191906000526020600020905b815481526020019060010190808311611839575b505050505091505092915050565b6004545b90565b600060056000846040516020018082805190602001908083835b6020831061189b5780518252601f19909201916020918201910161187c565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014156118f5575060006110a8565b600061190084612495565b905060056000856040516020018082805190602001908083835b602083106119395780518252601f19909201916020918201910161191a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001208152602001908152602001600020546000141580156119b257508260048281548110151561199e57fe5b906000526020600020906008020160010154145b949350505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611a0857600080fd5b505afa158015611a1c573d6000803e3d6000fd5b505050506040513d6020811015611a3257600080fd5b50516001600160a01b03163314611a875760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611adc6040518060200160405280600081525085858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506001925060029150612d0e9050565b6002556003555050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611b3457600080fd5b505afa158015611b48573d6000803e3d6000fd5b505050506040513d6020811015611b5e57600080fd5b50516001600160a01b03163314611bb35760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611bf582828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250611862915050565b1515600114611c465760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b6000611c8783838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b90506002600482815481101515611c9a57fe5b9060005260206000209060080201600101819055507fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c600482815481101515611cdf57fe5b9060005260206000209060080201600001600483815481101515611cff57fe5b9060005260206000209060080201600201600484815481101515611d1f57fe5b9060005260206000209060080201600401600485815481101515611d3f57fe5b906000526020600020906008020160060154600260405180806020018060200180602001868152602001858152602001848103845289818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611df05780601f10611dc557610100808354040283529160200191611df0565b820191906000526020600020905b815481529060010190602001808311611dd357829003601f168201915b5050848103835288546002600019610100600184161502019091160480825260209091019089908015611e645780601f10611e3957610100808354040283529160200191611e64565b820191906000526020600020905b815481529060010190602001808311611e4757829003601f168201915b5050848103825287546002600019610100600184161502019091160480825260209091019088908015611ed85780601f10611ead57610100808354040283529160200191611ed8565b820191906000526020600020905b815481529060010190602001808311611ebb57829003601f168201915b50509850505050505050505060405180910390a1505050565b6060806060600080611f3887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061240792505050565b1515611fa757868660008083838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820183528382528251908101909252918152949d509b50929950939750919550612250945050505050565b6000611fe888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b9050600481815481101515611ff957fe5b906000526020600020906008020160000160048281548110151561201957fe5b906000526020600020906008020160020160048381548110151561203957fe5b906000526020600020906008020160040160048481548110151561205957fe5b90600052602060002090600802016006015460048581548110151561207a57fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156121215780601f106120f657610100808354040283529160200191612121565b820191906000526020600020905b81548152906001019060200180831161210457829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a50899450925084019050828280156121af5780601f10612184576101008083540402835291602001916121af565b820191906000526020600020905b81548152906001019060200180831161219257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529599508894509250840190508282801561223d5780601f106122125761010080835404028352916020019161223d565b820191906000526020600020905b81548152906001019060200180831161222057829003601f168201915b5050505050925095509550955095509550505b9295509295909350565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156122a857600080fd5b505afa1580156122bc573d6000803e3d6000fd5b505050506040513d60208110156122d257600080fd5b50516001600160a01b031633146123275760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b81818080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061236992508391506124079050565b156123ae5760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b6124026040518060200160405280600081525084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250829150612d0e9050565b505050565b600060056000836040516020018082805190602001908083835b602083106124405780518252601f199092019160209182019101612421565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014159050919050565b6000600160056000846040516020018082805190602001908083835b602083106124d05780518252601f1990920191602091820191016124b1565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b61252d816002611862565b151560011461257057604051600160e51b62461bcd028152600401808060200182810382526034815260200180613ad76034913960400191505060405180910390fd5b600061257b82612495565b9050600360048281548110151561258e57fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156125d357fe5b90600052602060002090600802016000016004838154811015156125f357fe5b906000526020600020906008020160020160048481548110151561261357fe5b906000526020600020906008020160040160048581548110151561263357fe5b9060005260206000209060080201600601546003604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156126e45780601f106126b9576101008083540402835291602001916126e4565b820191906000526020600020905b8154815290600101906020018083116126c757829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156127585780601f1061272d57610100808354040283529160200191612758565b820191906000526020600020905b81548152906001019060200180831161273b57829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156127cc5780601f106127a1576101008083540402835291602001916127cc565b820191906000526020600020905b8154815290600101906020018083116127af57829003601f168201915b50509850505050505050505060405180910390a15050565b6127ef816004611862565b15156001146128485760408051600160e51b62461bcd02815260206004820152601a60248201527f6f7267206e6f7420696e2073757370656e646564207374617465000000000000604482015290519081900360640190fd5b600061285382612495565b9050600560048281548110151561286657fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156128ab57fe5b90600052602060002090600802016000016004838154811015156128cb57fe5b90600052602060002090600802016002016004848154811015156128eb57fe5b906000526020600020906008020160040160048581548110151561290b57fe5b9060005260206000209060080201600601546005604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156126e45780601f106126b9576101008083540402835291602001916126e4565b61299c816003611862565b15156001146129ed5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b60006129f882612495565b905060048082815481101515612a0a57fe5b9060005260206000209060080201600101819055507f73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96600482815481101515612a4f57fe5b9060005260206000209060080201600001600483815481101515612a6f57fe5b9060005260206000209060080201600201600484815481101515612a8f57fe5b9060005260206000209060080201600401600485815481101515612aaf57fe5b600091825260209182902060066008909202010154604080516060810183905260808082528754600260001961010060018416150201909116049082018190529293909283929183019183019060a084019089908015612b505780601f10612b2557610100808354040283529160200191612b50565b820191906000526020600020905b815481529060010190602001808311612b3357829003601f168201915b5050848103835287546002600019610100600184161502019091160480825260209091019088908015612bc45780601f10612b9957610100808354040283529160200191612bc4565b820191906000526020600020905b815481529060010190602001808311612ba757829003601f168201915b5050848103825286546002600019610100600184161502019091160480825260209091019087908015612c385780601f10612c0d57610100808354040283529160200191612c38565b820191906000526020600020905b815481529060010190602001808311612c1b57829003601f168201915b505097505050505050505060405180910390a15050565b612c5a816005611862565b1515600114612cab5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b6000612cb682612495565b90506002600482815481101515612cc957fe5b9060005260206000209060080201600101819055507f882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f600482815481101515612a4f57fe5b600080806001851415612d9057856040516020018082805190602001908083835b60208310612d4e5780518252601f199092019160209182019101612d2f565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001209150612ecf565b866040516020018082805190602001908083835b60208310612dc35780518252601f199092019160209182019101612da4565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120925086866040516020018083805190602001908083835b60208310612e345780518252601f199092019160209182019101612e15565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b60208310612e905780518252601f199092019160209182019101612e71565b6001836020036101000a038019825116818451168082178552505050505050905001925050506040516020818303038152906040528051906020012091505b600680546001908101918290556000848152600560205260408120929092556004805491612eff9190830161382a565b90508560011415612fc45785600482815481101515612f1a57fe5b9060005260206000209060080201600601819055506000600482815481101515612f4057fe5b90600052602060002090600802016005018190555086600482815481101515612f6557fe5b90600052602060002090600802016003019080519060200190612f89929190613856565b5086600482815481101515612f9a57fe5b90600052602060002090600802016004019080519060200190612fbe929190613856565b50613308565b600084815260056020526040902054600354600480546000199093019450909184908110612fee57fe5b6000918252602090912060076008909202010154106130575760408051600160e51b62461bcd02815260206004820152601660248201527f62726561647468206c6576656c20657863656564656400000000000000000000604482015290519081900360640190fd5b600254600480548490811061306857fe5b9060005260206000209060080201600601541015156130d15760408051600160e51b62461bcd02815260206004820152601460248201527f6465707468206c6576656c206578636565646564000000000000000000000000604482015290519081900360640190fd5b60048054839081106130df57fe5b90600052602060002090600802016006015460010160048281548110151561310357fe5b9060005260206000209060080201600601819055508160048281548110151561312857fe5b6000918252602090912060056008909202010155600480548390811061314a57fe5b906000526020600020906008020160040160048281548110151561316a57fe5b9060005260206000209060080201600401908054600181600116156101000203166002900461319a9291906138d4565b5060006004838154811015156131ac57fe5b906000526020600020906008020160070180548091906001016131cf9190613949565b9050816004848154811015156131e157fe5b9060005260206000209060080201600701828154811015156131ff57fe5b906000526020600020018190555088886040516020018083805190602001908083835b602083106132415780518252601f199092019160209182019101613222565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b6020831061329d5780518252601f19909201916020918201910161327e565b6001836020036101000a038019825116818451168082178552505050505050905001925050506040516020818303038152906040526004838154811015156132e157fe5b90600052602060002090600802016003019080519060200190613305929190613856565b50505b8660048281548110151561331857fe5b9060005260206000209060080201600001908051906020019061333c929190613856565b508760048281548110151561334d57fe5b90600052602060002090600802016002019080519060200190613371929190613856565b508460048281548110151561338257fe5b90600052602060002090600802016001018190555084600114156135e2577f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156133d057fe5b90600052602060002090600802016000016004838154811015156133f057fe5b906000526020600020906008020160020160048481548110151561341057fe5b906000526020600020906008020160040160048581548110151561343057fe5b9060005260206000209060080201600601546001604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156134e15780601f106134b6576101008083540402835291602001916134e1565b820191906000526020600020905b8154815290600101906020018083116134c457829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156135555780601f1061352a57610100808354040283529160200191613555565b820191906000526020600020905b81548152906001019060200180831161353857829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156135c95780601f1061359e576101008083540402835291602001916135c9565b820191906000526020600020905b8154815290600101906020018083116135ac57829003601f168201915b50509850505050505050505060405180910390a1613820565b7fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c60048281548110151561361257fe5b906000526020600020906008020160000160048381548110151561363257fe5b906000526020600020906008020160020160048481548110151561365257fe5b906000526020600020906008020160040160048581548110151561367257fe5b9060005260206000209060080201600601546002604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156137235780601f106136f857610100808354040283529160200191613723565b820191906000526020600020905b81548152906001019060200180831161370657829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156137975780601f1061376c57610100808354040283529160200191613797565b820191906000526020600020905b81548152906001019060200180831161377a57829003601f168201915b505084810382528754600260001961010060018416150201909116048082526020909101908890801561380b5780601f106137e05761010080835404028352916020019161380b565b820191906000526020600020905b8154815290600101906020018083116137ee57829003601f168201915b50509850505050505050505060405180910390a15b5050505050505050565b81548183558181111561240257600802816008028360005260206000209182019101612402919061396d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061389757805160ff19168380011785556138c4565b828001600101855582156138c4579182015b828111156138c45782518255916020019190600101906138a9565b506138d09291506139e4565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061390d57805485556138c4565b828001600101855582156138c457600052602060002091601f016020900482015b828111156138c457825482559160010191906001019061392e565b815481835581811115612402576000838152602090206124029181019083016139e4565b61185f91905b808211156138d057600061398782826139fe565b600182016000905560028201600061399f91906139fe565b6139ad6003830160006139fe565b6139bb6004830160006139fe565b600582016000905560068201600090556007820160006139db9190613a45565b50600801613973565b61185f91905b808211156138d057600081556001016139ea565b50805460018160011615610100020316600290046000825580601f10613a245750613a42565b601f016020900490600052602060002090810190613a4291906139e4565b50565b5080546000825590600052602060002090810190613a4291906139e456fe696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f7765646e6f742061206d6173746572206f72672e206f7065726174696f6e206e6f7420616c6c6f7765646f72672073746174757320646f6573206e6f7420616c6c6f7720746865206f7065726174696f6e6f7267206e6f7420696e20617070726f766564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e65a165627a7a72305820e20472358f6cc444b5476d1b471677de46e77b7421a1bcae4549f346a29e10870029" + +// DeployOrgManager deploys a new Ethereum contract, binding an instance of OrgManager to it. +func DeployOrgManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *OrgManager, error) { + parsed, err := abi.JSON(strings.NewReader(OrgManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OrgManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &OrgManager{OrgManagerCaller: OrgManagerCaller{contract: contract}, OrgManagerTransactor: OrgManagerTransactor{contract: contract}, OrgManagerFilterer: OrgManagerFilterer{contract: contract}}, nil +} + +// OrgManager is an auto generated Go binding around an Ethereum contract. +type OrgManager struct { + OrgManagerCaller // Read-only binding to the contract + OrgManagerTransactor // Write-only binding to the contract + OrgManagerFilterer // Log filterer for contract events +} + +// OrgManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type OrgManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OrgManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OrgManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OrgManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type OrgManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OrgManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OrgManagerSession struct { + Contract *OrgManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OrgManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OrgManagerCallerSession struct { + Contract *OrgManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OrgManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OrgManagerTransactorSession struct { + Contract *OrgManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OrgManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type OrgManagerRaw struct { + Contract *OrgManager // Generic contract binding to access the raw methods on +} + +// OrgManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OrgManagerCallerRaw struct { + Contract *OrgManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// OrgManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OrgManagerTransactorRaw struct { + Contract *OrgManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOrgManager creates a new instance of OrgManager, bound to a specific deployed contract. +func NewOrgManager(address common.Address, backend bind.ContractBackend) (*OrgManager, error) { + contract, err := bindOrgManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &OrgManager{OrgManagerCaller: OrgManagerCaller{contract: contract}, OrgManagerTransactor: OrgManagerTransactor{contract: contract}, OrgManagerFilterer: OrgManagerFilterer{contract: contract}}, nil +} + +// NewOrgManagerCaller creates a new read-only instance of OrgManager, bound to a specific deployed contract. +func NewOrgManagerCaller(address common.Address, caller bind.ContractCaller) (*OrgManagerCaller, error) { + contract, err := bindOrgManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &OrgManagerCaller{contract: contract}, nil +} + +// NewOrgManagerTransactor creates a new write-only instance of OrgManager, bound to a specific deployed contract. +func NewOrgManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*OrgManagerTransactor, error) { + contract, err := bindOrgManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &OrgManagerTransactor{contract: contract}, nil +} + +// NewOrgManagerFilterer creates a new log filterer instance of OrgManager, bound to a specific deployed contract. +func NewOrgManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*OrgManagerFilterer, error) { + contract, err := bindOrgManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &OrgManagerFilterer{contract: contract}, nil +} + +// bindOrgManager binds a generic wrapper to an already deployed contract. +func bindOrgManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OrgManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_OrgManager *OrgManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _OrgManager.Contract.OrgManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_OrgManager *OrgManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OrgManager.Contract.OrgManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_OrgManager *OrgManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _OrgManager.Contract.OrgManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_OrgManager *OrgManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _OrgManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_OrgManager *OrgManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OrgManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_OrgManager *OrgManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _OrgManager.Contract.contract.Transact(opts, method, params...) +} + +// CheckOrgActive is a free data retrieval call binding the contract method 0x3fd62ae7. +// +// Solidity: function checkOrgActive(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerCaller) CheckOrgActive(opts *bind.CallOpts, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "checkOrgActive", _orgId) + return *ret0, err +} + +// CheckOrgActive is a free data retrieval call binding the contract method 0x3fd62ae7. +// +// Solidity: function checkOrgActive(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerSession) CheckOrgActive(_orgId string) (bool, error) { + return _OrgManager.Contract.CheckOrgActive(&_OrgManager.CallOpts, _orgId) +} + +// CheckOrgActive is a free data retrieval call binding the contract method 0x3fd62ae7. +// +// Solidity: function checkOrgActive(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerCallerSession) CheckOrgActive(_orgId string) (bool, error) { + return _OrgManager.Contract.CheckOrgActive(&_OrgManager.CallOpts, _orgId) +} + +// CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. +// +// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerCaller) CheckOrgExists(opts *bind.CallOpts, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "checkOrgExists", _orgId) + return *ret0, err +} + +// CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. +// +// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerSession) CheckOrgExists(_orgId string) (bool, error) { + return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) +} + +// CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. +// +// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +func (_OrgManager *OrgManagerCallerSession) CheckOrgExists(_orgId string) (bool, error) { + return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) +} + +// CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. +// +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +func (_OrgManager *OrgManagerCaller) CheckOrgStatus(opts *bind.CallOpts, _orgId string, _orgStatus *big.Int) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "checkOrgStatus", _orgId, _orgStatus) + return *ret0, err +} + +// CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. +// +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +func (_OrgManager *OrgManagerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { + return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) +} + +// CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. +// +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +func (_OrgManager *OrgManagerCallerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { + return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) +} + +// GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. +// +// Solidity: function getNumberOfOrgs() constant returns(uint256) +func (_OrgManager *OrgManagerCaller) GetNumberOfOrgs(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "getNumberOfOrgs") + return *ret0, err +} + +// GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. +// +// Solidity: function getNumberOfOrgs() constant returns(uint256) +func (_OrgManager *OrgManagerSession) GetNumberOfOrgs() (*big.Int, error) { + return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) +} + +// GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. +// +// Solidity: function getNumberOfOrgs() constant returns(uint256) +func (_OrgManager *OrgManagerCallerSession) GetNumberOfOrgs() (*big.Int, error) { + return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) +} + +// GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. +// +// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCaller) GetOrgDetails(opts *bind.CallOpts, _orgId string) (string, string, string, *big.Int, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _OrgManager.contract.Call(opts, out, "getOrgDetails", _orgId) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. +// +// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) +} + +// GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. +// +// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCallerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) +} + +// GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. +// +// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCaller) GetOrgInfo(opts *bind.CallOpts, _orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(string) + ret3 = new(*big.Int) + ret4 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + ret4, + } + err := _OrgManager.contract.Call(opts, out, "getOrgInfo", _orgIndex) + return *ret0, *ret1, *ret2, *ret3, *ret4, err +} + +// GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. +// +// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) +} + +// GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. +// +// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +func (_OrgManager *OrgManagerCallerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) +} + +// GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. +// +// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +func (_OrgManager *OrgManagerCaller) GetSubOrgIndexes(opts *bind.CallOpts, _orgId string) ([]*big.Int, error) { + var ( + ret0 = new([]*big.Int) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "getSubOrgIndexes", _orgId) + return *ret0, err +} + +// GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. +// +// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +func (_OrgManager *OrgManagerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { + return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) +} + +// GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. +// +// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +func (_OrgManager *OrgManagerCallerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { + return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) +} + +// GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. +// +// Solidity: function getUltimateParent(string _orgId) constant returns(string) +func (_OrgManager *OrgManagerCaller) GetUltimateParent(opts *bind.CallOpts, _orgId string) (string, error) { + var ( + ret0 = new(string) + ) + out := ret0 + err := _OrgManager.contract.Call(opts, out, "getUltimateParent", _orgId) + return *ret0, err +} + +// GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. +// +// Solidity: function getUltimateParent(string _orgId) constant returns(string) +func (_OrgManager *OrgManagerSession) GetUltimateParent(_orgId string) (string, error) { + return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) +} + +// GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. +// +// Solidity: function getUltimateParent(string _orgId) constant returns(string) +func (_OrgManager *OrgManagerCallerSession) GetUltimateParent(_orgId string) (string, error) { + return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf9953de5. +// +// Solidity: function addOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactor) AddOrg(opts *bind.TransactOpts, _orgId string) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "addOrg", _orgId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf9953de5. +// +// Solidity: function addOrg(string _orgId) returns() +func (_OrgManager *OrgManagerSession) AddOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddOrg(&_OrgManager.TransactOpts, _orgId) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xf9953de5. +// +// Solidity: function addOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactorSession) AddOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddOrg(&_OrgManager.TransactOpts, _orgId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x1f953480. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId) returns() +func (_OrgManager *OrgManagerTransactor) AddSubOrg(opts *bind.TransactOpts, _pOrgId string, _orgId string) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "addSubOrg", _pOrgId, _orgId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x1f953480. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId) returns() +func (_OrgManager *OrgManagerSession) AddSubOrg(_pOrgId string, _orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddSubOrg(&_OrgManager.TransactOpts, _pOrgId, _orgId) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x1f953480. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId) returns() +func (_OrgManager *OrgManagerTransactorSession) AddSubOrg(_pOrgId string, _orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.AddSubOrg(&_OrgManager.TransactOpts, _pOrgId, _orgId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xe3028316. +// +// Solidity: function approveOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactor) ApproveOrg(opts *bind.TransactOpts, _orgId string) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "approveOrg", _orgId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xe3028316. +// +// Solidity: function approveOrg(string _orgId) returns() +func (_OrgManager *OrgManagerSession) ApproveOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrg(&_OrgManager.TransactOpts, _orgId) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xe3028316. +// +// Solidity: function approveOrg(string _orgId) returns() +func (_OrgManager *OrgManagerTransactorSession) ApproveOrg(_orgId string) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrg(&_OrgManager.TransactOpts, _orgId) +} + +// ApproveOrgStatusUpdate is a paid mutator transaction binding the contract method 0x14f775f9. +// +// Solidity: function approveOrgStatusUpdate(string _orgId, uint256 _action) returns() +func (_OrgManager *OrgManagerTransactor) ApproveOrgStatusUpdate(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "approveOrgStatusUpdate", _orgId, _action) +} + +// ApproveOrgStatusUpdate is a paid mutator transaction binding the contract method 0x14f775f9. +// +// Solidity: function approveOrgStatusUpdate(string _orgId, uint256 _action) returns() +func (_OrgManager *OrgManagerSession) ApproveOrgStatusUpdate(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrgStatusUpdate(&_OrgManager.TransactOpts, _orgId, _action) +} + +// ApproveOrgStatusUpdate is a paid mutator transaction binding the contract method 0x14f775f9. +// +// Solidity: function approveOrgStatusUpdate(string _orgId, uint256 _action) returns() +func (_OrgManager *OrgManagerTransactorSession) ApproveOrgStatusUpdate(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.ApproveOrgStatusUpdate(&_OrgManager.TransactOpts, _orgId, _action) +} + +// SetUpOrg is a paid mutator transaction binding the contract method 0x9e58eb9f. +// +// Solidity: function setUpOrg(string _orgId, uint256 _breadth, uint256 _depth) returns() +func (_OrgManager *OrgManagerTransactor) SetUpOrg(opts *bind.TransactOpts, _orgId string, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "setUpOrg", _orgId, _breadth, _depth) +} + +// SetUpOrg is a paid mutator transaction binding the contract method 0x9e58eb9f. +// +// Solidity: function setUpOrg(string _orgId, uint256 _breadth, uint256 _depth) returns() +func (_OrgManager *OrgManagerSession) SetUpOrg(_orgId string, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.SetUpOrg(&_OrgManager.TransactOpts, _orgId, _breadth, _depth) +} + +// SetUpOrg is a paid mutator transaction binding the contract method 0x9e58eb9f. +// +// Solidity: function setUpOrg(string _orgId, uint256 _breadth, uint256 _depth) returns() +func (_OrgManager *OrgManagerTransactorSession) SetUpOrg(_orgId string, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.SetUpOrg(&_OrgManager.TransactOpts, _orgId, _breadth, _depth) +} + +// UpdateOrg is a paid mutator transaction binding the contract method 0x0cc27493. +// +// Solidity: function updateOrg(string _orgId, uint256 _action) returns(uint256) +func (_OrgManager *OrgManagerTransactor) UpdateOrg(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.contract.Transact(opts, "updateOrg", _orgId, _action) +} + +// UpdateOrg is a paid mutator transaction binding the contract method 0x0cc27493. +// +// Solidity: function updateOrg(string _orgId, uint256 _action) returns(uint256) +func (_OrgManager *OrgManagerSession) UpdateOrg(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.UpdateOrg(&_OrgManager.TransactOpts, _orgId, _action) +} + +// UpdateOrg is a paid mutator transaction binding the contract method 0x0cc27493. +// +// Solidity: function updateOrg(string _orgId, uint256 _action) returns(uint256) +func (_OrgManager *OrgManagerTransactorSession) UpdateOrg(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _OrgManager.Contract.UpdateOrg(&_OrgManager.TransactOpts, _orgId, _action) +} + +// OrgManagerOrgApprovedIterator is returned from FilterOrgApproved and is used to iterate over the raw logs and unpacked data for OrgApproved events raised by the OrgManager contract. +type OrgManagerOrgApprovedIterator struct { + Event *OrgManagerOrgApproved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgApprovedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgApproved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgApprovedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgApprovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgApproved represents a OrgApproved event raised by the OrgManager contract. +type OrgManagerOrgApproved struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgApproved is a free log retrieval operation binding the contract event 0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c. +// +// Solidity: event OrgApproved(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) FilterOrgApproved(opts *bind.FilterOpts) (*OrgManagerOrgApprovedIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgApproved") + if err != nil { + return nil, err + } + return &OrgManagerOrgApprovedIterator{contract: _OrgManager.contract, event: "OrgApproved", logs: logs, sub: sub}, nil +} + +var OrgApprovedTopicHash = "0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c" + +// WatchOrgApproved is a free log subscription operation binding the contract event 0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c. +// +// Solidity: event OrgApproved(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) WatchOrgApproved(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgApproved) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgApproved") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgApproved) + if err := _OrgManager.contract.UnpackLog(event, "OrgApproved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgApproved is a log parse operation binding the contract event 0xd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c. +// +// Solidity: event OrgApproved(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) ParseOrgApproved(log types.Log) (*OrgManagerOrgApproved, error) { + event := new(OrgManagerOrgApproved) + if err := _OrgManager.contract.UnpackLog(event, "OrgApproved", log); err != nil { + return nil, err + } + return event, nil +} + +// OrgManagerOrgPendingApprovalIterator is returned from FilterOrgPendingApproval and is used to iterate over the raw logs and unpacked data for OrgPendingApproval events raised by the OrgManager contract. +type OrgManagerOrgPendingApprovalIterator struct { + Event *OrgManagerOrgPendingApproval // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgPendingApprovalIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgPendingApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgPendingApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgPendingApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgPendingApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgPendingApproval represents a OrgPendingApproval event raised by the OrgManager contract. +type OrgManagerOrgPendingApproval struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Status *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgPendingApproval is a free log retrieval operation binding the contract event 0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b. +// +// Solidity: event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) FilterOrgPendingApproval(opts *bind.FilterOpts) (*OrgManagerOrgPendingApprovalIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgPendingApproval") + if err != nil { + return nil, err + } + return &OrgManagerOrgPendingApprovalIterator{contract: _OrgManager.contract, event: "OrgPendingApproval", logs: logs, sub: sub}, nil +} + +var OrgPendingApprovalTopicHash = "0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b" + +// WatchOrgPendingApproval is a free log subscription operation binding the contract event 0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b. +// +// Solidity: event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) WatchOrgPendingApproval(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgPendingApproval) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgPendingApproval") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgPendingApproval) + if err := _OrgManager.contract.UnpackLog(event, "OrgPendingApproval", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgPendingApproval is a log parse operation binding the contract event 0x0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b. +// +// Solidity: event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, uint256 _level, uint256 _status) +func (_OrgManager *OrgManagerFilterer) ParseOrgPendingApproval(log types.Log) (*OrgManagerOrgPendingApproval, error) { + event := new(OrgManagerOrgPendingApproval) + if err := _OrgManager.contract.UnpackLog(event, "OrgPendingApproval", log); err != nil { + return nil, err + } + return event, nil +} + +// OrgManagerOrgSuspendedIterator is returned from FilterOrgSuspended and is used to iterate over the raw logs and unpacked data for OrgSuspended events raised by the OrgManager contract. +type OrgManagerOrgSuspendedIterator struct { + Event *OrgManagerOrgSuspended // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgSuspendedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgSuspendedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgSuspendedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgSuspended represents a OrgSuspended event raised by the OrgManager contract. +type OrgManagerOrgSuspended struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgSuspended is a free log retrieval operation binding the contract event 0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96. +// +// Solidity: event OrgSuspended(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) FilterOrgSuspended(opts *bind.FilterOpts) (*OrgManagerOrgSuspendedIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgSuspended") + if err != nil { + return nil, err + } + return &OrgManagerOrgSuspendedIterator{contract: _OrgManager.contract, event: "OrgSuspended", logs: logs, sub: sub}, nil +} + +var OrgSuspendedTopicHash = "0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96" + +// WatchOrgSuspended is a free log subscription operation binding the contract event 0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96. +// +// Solidity: event OrgSuspended(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) WatchOrgSuspended(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgSuspended) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgSuspended") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgSuspended) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspended", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgSuspended is a log parse operation binding the contract event 0x73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96. +// +// Solidity: event OrgSuspended(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) ParseOrgSuspended(log types.Log) (*OrgManagerOrgSuspended, error) { + event := new(OrgManagerOrgSuspended) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspended", log); err != nil { + return nil, err + } + return event, nil +} + +// OrgManagerOrgSuspensionRevokedIterator is returned from FilterOrgSuspensionRevoked and is used to iterate over the raw logs and unpacked data for OrgSuspensionRevoked events raised by the OrgManager contract. +type OrgManagerOrgSuspensionRevokedIterator struct { + Event *OrgManagerOrgSuspensionRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OrgManagerOrgSuspensionRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspensionRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OrgManagerOrgSuspensionRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OrgManagerOrgSuspensionRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OrgManagerOrgSuspensionRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OrgManagerOrgSuspensionRevoked represents a OrgSuspensionRevoked event raised by the OrgManager contract. +type OrgManagerOrgSuspensionRevoked struct { + OrgId string + PorgId string + UltParent string + Level *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOrgSuspensionRevoked is a free log retrieval operation binding the contract event 0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f. +// +// Solidity: event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) FilterOrgSuspensionRevoked(opts *bind.FilterOpts) (*OrgManagerOrgSuspensionRevokedIterator, error) { + + logs, sub, err := _OrgManager.contract.FilterLogs(opts, "OrgSuspensionRevoked") + if err != nil { + return nil, err + } + return &OrgManagerOrgSuspensionRevokedIterator{contract: _OrgManager.contract, event: "OrgSuspensionRevoked", logs: logs, sub: sub}, nil +} + +var OrgSuspensionRevokedTopicHash = "0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f" + +// WatchOrgSuspensionRevoked is a free log subscription operation binding the contract event 0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f. +// +// Solidity: event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) WatchOrgSuspensionRevoked(opts *bind.WatchOpts, sink chan<- *OrgManagerOrgSuspensionRevoked) (event.Subscription, error) { + + logs, sub, err := _OrgManager.contract.WatchLogs(opts, "OrgSuspensionRevoked") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OrgManagerOrgSuspensionRevoked) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspensionRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOrgSuspensionRevoked is a log parse operation binding the contract event 0x882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f. +// +// Solidity: event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, uint256 _level) +func (_OrgManager *OrgManagerFilterer) ParseOrgSuspensionRevoked(log types.Log) (*OrgManagerOrgSuspensionRevoked, error) { + event := new(OrgManagerOrgSuspensionRevoked) + if err := _OrgManager.contract.UnpackLog(event, "OrgSuspensionRevoked", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v2/bind/permission_impl.go b/permission/v2/bind/permission_impl.go new file mode 100644 index 0000000000..663308606a --- /dev/null +++ b/permission/v2/bind/permission_impl.go @@ -0,0 +1,1035 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PermImplABI is the input ABI used to generate the binding from. +const PermImplABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"updateAccountStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_access\",\"type\":\"uint256\"},{\"name\":\"_voter\",\"type\":\"bool\"},{\"name\":\"_admin\",\"type\":\"bool\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addNewRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminOrg\",\"type\":\"string\"},{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"}],\"name\":\"setPolicy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"startBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"updateOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"assignAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updateNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"}],\"name\":\"connectionAllowed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"addAdminAccount\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"removeRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_pOrgId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addSubOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"validateAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"}],\"name\":\"addAdminNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"assignAccountRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_sender\",\"type\":\"address\"},{\"name\":\"_target\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gasPrice\",\"type\":\"uint256\"},{\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"name\":\"_payload\",\"type\":\"bytes\"}],\"name\":\"transactionAllowed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"isOrgAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_breadth\",\"type\":\"uint256\"},{\"name\":\"_depth\",\"type\":\"uint256\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_action\",\"type\":\"uint256\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"updateNodeStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getPolicyDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"isNetworkAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"startBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"addNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getPendingOp\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminOrg\",\"type\":\"string\"},{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"},{\"name\":\"_networkBootStatus\",\"type\":\"bool\"}],\"name\":\"setMigrationPolicy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_caller\",\"type\":\"address\"}],\"name\":\"approveOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"},{\"name\":\"_orgManager\",\"type\":\"address\"},{\"name\":\"_rolesManager\",\"type\":\"address\"},{\"name\":\"_accountManager\",\"type\":\"address\"},{\"name\":\"_voterManager\",\"type\":\"address\"},{\"name\":\"_nodeManager\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_networkBootStatus\",\"type\":\"bool\"}],\"name\":\"PermissionsInitialized\",\"type\":\"event\"}]" + +var PermImplParsedABI, _ = abi.JSON(strings.NewReader(PermImplABI)) + +// PermImplBin is the compiled bytecode used for deploying new contracts. +var PermImplBin = "0x60806040526003600955600a805460ff191690553480156200002057600080fd5b5060405160c08062008587833981018060405260c08110156200004257600080fd5b508051602082015160408301516060840151608085015160a090950151600580546001600160a01b039687166001600160a01b03199182161790915560048054958716958216959095179094556001805493861693851693909317909255600080549185169184169190911790556002805494841694831694909417909355600380549290931691161790556184a980620000de6000396000f3fe608060405234801561001057600080fd5b50600436106101e55760003560e01c8063888430411161010f578063cc9ba6fa116100a2578063ecad01d511610071578063ecad01d5146117db578063f346a3a7146119a5578063f5ad584a14611b11578063f75f0a0614611c21576101e5565b8063cc9ba6fa146112c4578063d1aa0c2014611418578063d621d9571461143e578063e91b0e1914611608576101e5565b8063a042bf40116100de578063a042bf4014610e8b578063a5843f0814611055578063b554656414611078578063b9b7fe6c146110f5576101e5565b80638884304114610b7a5780638baa819114610bf9578063936421d514610d3d5780639bd3810114610dd7576101e5565b806345a59e5b116101875780635ca5adbe116101565780635ca5adbe146107b957806368a61273146108805780636b568d76146109fc5780638683c7fe14610ab0576101e5565b806345a59e5b1461064a5780634b20f45f1461070c5780634cbfa82e1461078b5780634fe57e7a14610793576101e5565b80631c249912116101c35780631c2499121461045e5780633cf5f33b146104dd578063404bf3eb1461055a57806344478e791461062e576101e5565b806304e81f1e146101ea5780631b04c276146102735780631b61022014610350575b600080fd5b6102716004803603608081101561020057600080fd5b810190602081018135600160201b81111561021a57600080fd5b82018360208201111561022c57600080fd5b803590602001918460018302840111600160201b8311171561024d57600080fd5b91935091506001600160a01b03813581169160208101359160409091013516611df4565b005b610271600480360360c081101561028957600080fd5b810190602081018135600160201b8111156102a357600080fd5b8201836020820111156102b557600080fd5b803590602001918460018302840111600160201b831117156102d657600080fd5b919390929091602081019035600160201b8111156102f357600080fd5b82018360208201111561030557600080fd5b803590602001918460018302840111600160201b8311171561032657600080fd5b919350915080359060208101351515906040810135151590606001356001600160a01b0316612047565b6102716004803603606081101561036657600080fd5b810190602081018135600160201b81111561038057600080fd5b82018360208201111561039257600080fd5b803590602001918460018302840111600160201b831117156103b357600080fd5b919390929091602081019035600160201b8111156103d057600080fd5b8201836020820111156103e257600080fd5b803590602001918460018302840111600160201b8311171561040357600080fd5b919390929091602081019035600160201b81111561042057600080fd5b82018360208201111561043257600080fd5b803590602001918460018302840111600160201b8311171561045357600080fd5b509092509050612309565b6102716004803603606081101561047457600080fd5b810190602081018135600160201b81111561048e57600080fd5b8201836020820111156104a057600080fd5b803590602001918460018302840111600160201b831117156104c157600080fd5b91935091506001600160a01b0381358116916020013516612448565b610271600480360360608110156104f357600080fd5b810190602081018135600160201b81111561050d57600080fd5b82018360208201111561051f57600080fd5b803590602001918460018302840111600160201b8311171561054057600080fd5b9193509150803590602001356001600160a01b0316612747565b6102716004803603608081101561057057600080fd5b810190602081018135600160201b81111561058a57600080fd5b82018360208201111561059c57600080fd5b803590602001918460018302840111600160201b831117156105bd57600080fd5b919390926001600160a01b0383351692604081019060200135600160201b8111156105e757600080fd5b8201836020820111156105f957600080fd5b803590602001918460018302840111600160201b8311171561061a57600080fd5b9193509150356001600160a01b0316612a4f565b610636612e0e565b604080519115158252519081900360200190f35b6106366004803603606081101561066057600080fd5b810190602081018135600160201b81111561067a57600080fd5b82018360208201111561068c57600080fd5b803590602001918460018302840111600160201b831117156106ad57600080fd5b919390929091602081019035600160201b8111156106ca57600080fd5b8201836020820111156106dc57600080fd5b803590602001918460018302840111600160201b831117156106fd57600080fd5b91935091503561ffff16612f6c565b6102716004803603606081101561072257600080fd5b810190602081018135600160201b81111561073c57600080fd5b82018360208201111561074e57600080fd5b803590602001918460018302840111600160201b8311171561076f57600080fd5b91935091506001600160a01b0381358116916020013516613070565b6106366132b9565b610271600480360360208110156107a957600080fd5b50356001600160a01b03166132c3565b610271600480360360608110156107cf57600080fd5b810190602081018135600160201b8111156107e957600080fd5b8201836020820111156107fb57600080fd5b803590602001918460018302840111600160201b8311171561081c57600080fd5b919390929091602081019035600160201b81111561083957600080fd5b82018360208201111561084b57600080fd5b803590602001918460018302840111600160201b8311171561086c57600080fd5b9193509150356001600160a01b03166135e4565b610271600480360360e081101561089657600080fd5b810190602081018135600160201b8111156108b057600080fd5b8201836020820111156108c257600080fd5b803590602001918460018302840111600160201b831117156108e357600080fd5b919390929091602081019035600160201b81111561090057600080fd5b82018360208201111561091257600080fd5b803590602001918460018302840111600160201b8311171561093357600080fd5b919390929091602081019035600160201b81111561095057600080fd5b82018360208201111561096257600080fd5b803590602001918460018302840111600160201b8311171561098357600080fd5b919390929091602081019035600160201b8111156109a057600080fd5b8201836020820111156109b257600080fd5b803590602001918460018302840111600160201b831117156109d357600080fd5b9193509150803561ffff90811691602081013590911690604001356001600160a01b0316613aa6565b61063660048036036040811015610a1257600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610a3c57600080fd5b820183602082011115610a4e57600080fd5b803590602001918460018302840111600160201b83111715610a6f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613ef1945050505050565b61027160048036036080811015610ac657600080fd5b810190602081018135600160201b811115610ae057600080fd5b820183602082011115610af257600080fd5b803590602001918460018302840111600160201b83111715610b1357600080fd5b919390929091602081019035600160201b811115610b3057600080fd5b820183602082011115610b4257600080fd5b803590602001918460018302840111600160201b83111715610b6357600080fd5b919350915061ffff81358116916020013516613fdf565b61027160048036036060811015610b9057600080fd5b810190602081018135600160201b811115610baa57600080fd5b820183602082011115610bbc57600080fd5b803590602001918460018302840111600160201b83111715610bdd57600080fd5b91935091506001600160a01b0381358116916020013516614228565b61027160048036036080811015610c0f57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610c3957600080fd5b820183602082011115610c4b57600080fd5b803590602001918460018302840111600160201b83111715610c6c57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610cbe57600080fd5b820183602082011115610cd057600080fd5b803590602001918460018302840111600160201b83111715610cf157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b0316915061467d9050565b610636600480360360c0811015610d5357600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b811115610d9957600080fd5b820183602082011115610dab57600080fd5b803590602001918460018302840111600160201b83111715610dcc57600080fd5b509092509050614b8b565b61063660048036036040811015610ded57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610e1757600080fd5b820183602082011115610e2957600080fd5b803590602001918460018302840111600160201b83111715610e4a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550615059945050505050565b610271600480360360c0811015610ea157600080fd5b810190602081018135600160201b811115610ebb57600080fd5b820183602082011115610ecd57600080fd5b803590602001918460018302840111600160201b83111715610eee57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610f4057600080fd5b820183602082011115610f5257600080fd5b803590602001918460018302840111600160201b83111715610f7357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610fc557600080fd5b820183602082011115610fd757600080fd5b803590602001918460018302840111600160201b83111715610ff857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b03169050615411565b6102716004803603604081101561106b57600080fd5b508035906020013561577d565b6102716004803603606081101561108e57600080fd5b810190602081018135600160201b8111156110a857600080fd5b8201836020820111156110ba57600080fd5b803590602001918460018302840111600160201b831117156110db57600080fd5b9193509150803590602001356001600160a01b0316615c4c565b610271600480360360e081101561110b57600080fd5b810190602081018135600160201b81111561112557600080fd5b82018360208201111561113757600080fd5b803590602001918460018302840111600160201b8311171561115857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156111aa57600080fd5b8201836020820111156111bc57600080fd5b803590602001918460018302840111600160201b831117156111dd57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561122f57600080fd5b82018360208201111561124157600080fd5b803590602001918460018302840111600160201b8311171561126257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff90811693506020830135169160408101359150606001356001600160a01b0316615fa5565b6112cc61619f565b604080518215156060820152608080825286519082015285519091829160208084019284019160a08501918a019080838360005b83811015611318578181015183820152602001611300565b50505050905090810190601f1680156113455780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b83811015611378578181015183820152602001611360565b50505050905090810190601f1680156113a55780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b838110156113d85781810151838201526020016113c0565b50505050905090810190601f1680156114055780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b6106366004803603602081101561142e57600080fd5b50356001600160a01b0316616372565b610271600480360360c081101561145457600080fd5b810190602081018135600160201b81111561146e57600080fd5b82018360208201111561148057600080fd5b803590602001918460018302840111600160201b831117156114a157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156114f357600080fd5b82018360208201111561150557600080fd5b803590602001918460018302840111600160201b8311171561152657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561157857600080fd5b82018360208201111561158a57600080fd5b803590602001918460018302840111600160201b831117156115ab57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b03169050616576565b610271600480360360e081101561161e57600080fd5b810190602081018135600160201b81111561163857600080fd5b82018360208201111561164a57600080fd5b803590602001918460018302840111600160201b8311171561166b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156116bd57600080fd5b8201836020820111156116cf57600080fd5b803590602001918460018302840111600160201b831117156116f057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561174257600080fd5b82018360208201111561175457600080fd5b803590602001918460018302840111600160201b8311171561177557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff833581169450602084013516926001600160a01b036040820135811693506060909101351690506169fb565b610271600480360360c08110156117f157600080fd5b810190602081018135600160201b81111561180b57600080fd5b82018360208201111561181d57600080fd5b803590602001918460018302840111600160201b8311171561183e57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561189057600080fd5b8201836020820111156118a257600080fd5b803590602001918460018302840111600160201b831117156118c357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561191557600080fd5b82018360208201111561192757600080fd5b803590602001918460018302840111600160201b8311171561194857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b0316905061713d565b611a13600480360360208110156119bb57600080fd5b810190602081018135600160201b8111156119d557600080fd5b8201836020820111156119e757600080fd5b803590602001918460018302840111600160201b83111715611a0857600080fd5b5090925090506173e7565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015611a72578181015183820152602001611a5a565b50505050905090810190601f168015611a9f5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015611ad2578181015183820152602001611aba565b50505050905090810190601f168015611aff5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61027160048036036080811015611b2757600080fd5b810190602081018135600160201b811115611b4157600080fd5b820183602082011115611b5357600080fd5b803590602001918460018302840111600160201b83111715611b7457600080fd5b919390929091602081019035600160201b811115611b9157600080fd5b820183602082011115611ba357600080fd5b803590602001918460018302840111600160201b83111715611bc457600080fd5b919390929091602081019035600160201b811115611be157600080fd5b820183602082011115611bf357600080fd5b803590602001918460018302840111600160201b83111715611c1457600080fd5b919350915035151561754f565b610271600480360360e0811015611c3757600080fd5b810190602081018135600160201b811115611c5157600080fd5b820183602082011115611c6357600080fd5b803590602001918460018302840111600160201b83111715611c8457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611cd657600080fd5b820183602082011115611ce857600080fd5b803590602001918460018302840111600160201b83111715611d0957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611d5b57600080fd5b820183602082011115611d6d57600080fd5b803590602001918460018302840111600160201b83111715611d8e57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff833581169450602084013516926001600160a01b0360408201358116935060609091013516905061763e565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611e4257600080fd5b505afa158015611e56573d6000803e3d6000fd5b505050506040513d6020811015611e6c57600080fd5b50516001600160a01b03163314611eb757604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8085858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611efb9250849150839050615059565b1515600114611f3e57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b8360011480611f4d5750836002145b80611f585750836003145b1515611f9857604051600160e51b62461bcd0281526004018080602001828103825260258152602001806183e96025913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03878116602483015260448201879052606060048301908152606483018a90529216916384b7a84a918a918a918a918a918190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561202657600080fd5b505af115801561203a573d6000803e3d6000fd5b5050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561209557600080fd5b505afa1580156120a9573d6000803e3d6000fd5b505050506040513d60208110156120bf57600080fd5b50516001600160a01b0316331461210a57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b85858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061214c9250839150617d129050565b15156001146121935760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b8187878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121d79250849150839050615059565b151560011461221a57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b600154604051600160e01b637b713579028152604481018990528715156064820152861515608482015260a06004820190815260a482018d90526001600160a01b0390921691637b713579918e918e918e918e918e918e918e91908190602481019060c4018a8a80828437600083820152601f01601f191690910184810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b1580156122e457600080fd5b505af11580156122f8573d6000803e3d6000fd5b505050505050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561235757600080fd5b505afa15801561236b573d6000803e3d6000fd5b505050506040513d602081101561238157600080fd5b50516001600160a01b031633146123cc57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff16156124185760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b6124246006888861831a565b506124316007868661831a565b5061243e6008848461831a565b5050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561249657600080fd5b505afa1580156124aa573d6000803e3d6000fd5b505050506040513d60208110156124c057600080fd5b50516001600160a01b0316331461250b57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061251581616372565b151560011461255857604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03858116602483015260046044830181905260608382019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156125ea57600080fd5b505af11580156125fe573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b03888116606483015260066084830181905260a060048401908152815460001960018216156101000201169590950460a4840181905291909316955063e98ac22d945091928a928a928a928692909182916024820191604481019160c490910190869080156126cb5780601f106126a0576101008083540402835291602001916126cb565b820191906000526020600020905b8154815290600101906020018083116126ae57829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b15801561272857600080fd5b505af115801561273c573d6000803e3d6000fd5b505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561279557600080fd5b505afa1580156127a9573d6000803e3d6000fd5b505050506040513d60208110156127bf57600080fd5b50516001600160a01b0316331461280a57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061281481616372565b151560011461285757604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b6004805460408051600160e01b630cc2749302815260248101879052928301908152604483018790526000926001600160a01b0390921691630cc27493918991899189918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b1580156128dd57600080fd5b505af11580156128f1573d6000803e3d6000fd5b505050506040513d602081101561290757600080fd5b505160028054604051600160e01b63e98ac22d0281526000606482018190526084820185905260a0600483019081526006805460001960018216156101000201169590950460a484018190529596506001600160a01b039093169463e98ac22d94938c938c939289929182916024820191604481019160c4909101908a9080156129d25780601f106129a7576101008083540402835291602001916129d2565b820191906000526020600020905b8154815290600101906020018083116129b557829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015612a2f57600080fd5b505af1158015612a43573d6000803e3d6000fd5b50505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015612a9d57600080fd5b505afa158015612ab1573d6000803e3d6000fd5b505050506040513d6020811015612ac757600080fd5b50516001600160a01b03163314612b1257604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612b549250839150617e019050565b1515600114612ba55760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b81612baf81616372565b1515600114612bf257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b600054604051600160e01b63e3483a9d0281526001600160a01b0388811660048301908152600160648401819052608060248501908152608485018d9052929094169363e3483a9d938b938e938e938d938d9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b158015612cb557600080fd5b505af1158015612cc9573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b038b8116606483015260046084830181905260a08382019081526006805460001960018216156101000201169690960460a4850181905292909416965063e98ac22d95508e938e938e9382916024810191604482019160c401908a908015612d8f5780601f10612d6457610100808354040283529160200191612d8f565b820191906000526020600020905b815481529060010190602001808311612d7257829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015612dec57600080fd5b505af1158015612e00573d6000803e3d6000fd5b505050505050505050505050565b60055460408051600160e21b63395c945702815290516000926001600160a01b03169163e572515c916004808301926020929190829003018186803b158015612e5657600080fd5b505afa158015612e6a573d6000803e3d6000fd5b505050506040513d6020811015612e8057600080fd5b50516001600160a01b03163314612ecb57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff1615612f175760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b600a805460ff1916600117908190556040805160ff9290921615158252517f04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf9181900360200190a1600a5460ff1691505b5090565b600a5460009060ff161515612f8357506001613067565b600354604051600160e01b6345a59e5b02815261ffff84166044820152606060048201908152606482018890526001600160a01b03909216916345a59e5b91899189918991899189919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505097505050505050505060206040518083038186803b15801561303857600080fd5b505afa15801561304c573d6000803e3d6000fd5b505050506040513d602081101561306257600080fd5b505190505b95945050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156130be57600080fd5b505afa1580156130d2573d6000803e3d6000fd5b505050506040513d60208110156130e857600080fd5b50516001600160a01b0316331461313357604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061313d81616372565b151560011461318057604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261321a939092909183018282801561320d5780601f106131e25761010080835404028352916020019161320d565b820191906000526020600020905b8154815290600101906020018083116131f057829003601f168201915b5050505050836006617ec6565b156132b257600054604051600160e11b63425bd4250281526001600160a01b0385811660248301526005604483018190526060600484019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561272857600080fd5b5050505050565b600a5460ff165b90565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561331157600080fd5b505afa158015613325573d6000803e3d6000fd5b505050506040513d602081101561333b57600080fd5b50516001600160a01b0316331461338657604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff16156133d25760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261346c939092909183018282801561345f5780601f106134345761010080835404028352916020019161345f565b820191906000526020600020905b81548152906001019060200180831161344257829003601f168201915b5050505050836001617fc0565b600054604051600160e01b63e3483a9d0281526001600160a01b038481166004830190815260026064840181905260806024850190815260068054600019600182161561010002011683900460848701819052949096169563e3483a9d95899591946007949390929091604481019160a490910190879080156135305780601f1061350557610100808354040283529160200191613530565b820191906000526020600020905b81548152906001019060200180831161351357829003601f168201915b50508381038252855460026000196101006001841615020190911604808252602090910190869080156135a45780601f10613579576101008083540402835291602001916135a4565b820191906000526020600020905b81548152906001019060200180831161358757829003601f168201915b50509650505050505050600060405180830381600087803b1580156135c857600080fd5b505af11580156135dc573d6000803e3d6000fd5b505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561363257600080fd5b505afa158015613646573d6000803e3d6000fd5b505050506040513d602081101561365c57600080fd5b50516001600160a01b031633146136a757604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506136e99250839150617d129050565b15156001146137305760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b8184848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506137749250849150839050615059565b15156001146137b757604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b604080516020808201908152600780546002600019610100600184161502019091160493830184905292909182916060909101908490801561383a5780601f1061380f5761010080835404028352916020019161383a565b820191906000526020600020905b81548152906001019060200180831161381d57829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120141580156139a7575060408051602080820190815260088054600260001961010060018416150201909116049383018490529290918291606090910190849080156139355780601f1061390a57610100808354040283529160200191613935565b820191906000526020600020905b81548152906001019060200180831161391857829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014155b15156139fd5760408051600160e51b62461bcd02815260206004820152601d60248201527f61646d696e20726f6c65732063616e6e6f742062652072656d6f766564000000604482015290519081900360640190fd5b60015460408051600160e11b63531a180902815260048101918252604481018a90526001600160a01b039092169163a6343012918b918b918b918b919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612dec57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015613af457600080fd5b505afa158015613b08573d6000803e3d6000fd5b505050506040513d6020811015613b1e57600080fd5b50516001600160a01b03163314613b6957604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8a8a8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613bab9250839150617e019050565b1515600114613bfc5760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b818c8c8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613c409250849150839050615059565b1515600114613c8357604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b600460009054906101000a90046001600160a01b03166001600160a01b0316631f9534808f8f8f8f6040518563ffffffff1660e01b81526004018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015613d3657600080fd5b505af1158015613d4a573d6000803e3d6000fd5b5050505060608e8e8e8e60405160200180858580828437600160f91b6017029201918252506001018383808284376040805191909301818103601f19018252909252509550508d1593506122f89250505057600354604051600160e01b634c57331102815261ffff808a1660448301528816606482015260a06004820190815260a482018d90526001600160a01b0390921691634c573311918e918e918e918e918e918e918a919081906024810190608481019060c4018b8b80828437600083820152601f01601f1916909101858103845289815260200190508989808284376000838201819052601f909101601f191690920186810384528751815287516020918201939189019250908190849084905b83811015613e74578181015183820152602001613e5c565b50505050905090810190601f168015613ea15780820380516001836020036101000a031916815260200191505b509a5050505050505050505050600060405180830381600087803b158015613ec857600080fd5b505af1158015613edc573d6000803e3d6000fd5b50505050505050505050505050505050505050565b6000805460408051600160e11b6335ab46bb0281526001600160a01b0386811660048301908152602483019384528651604484015286519190941693636b568d76938893889360649091019060208501908083838c5b83811015613f5f578181015183820152602001613f47565b50505050905090810190601f168015613f8c5780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015613faa57600080fd5b505afa158015613fbe573d6000803e3d6000fd5b505050506040513d6020811015613fd457600080fd5b505190505b92915050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561402d57600080fd5b505afa158015614041573d6000803e3d6000fd5b505050506040513d602081101561405757600080fd5b50516001600160a01b031633146140a257604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff16156140ee5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b600354604051600160e01b634530abe102815261ffff80861660448301528416606482015260a06004820190815260a482018990526001600160a01b0390921691634530abe1918a918a918a918a918a918a916006919081906024810190608481019060c4018b8b80828437600083820152601f01601f191690910185810384528981526020019050898980828437600083820152601f01601f1916909101858103835286546002600019610100600184161502019091160480825260209091019150869080156142005780601f106141d557610100808354040283529160200191614200565b820191906000526020600020905b8154815290600101906020018083116141e357829003601f168201915b50509a5050505050505050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561427657600080fd5b505afa15801561428a573d6000803e3d6000fd5b505050506040513d60208110156142a057600080fd5b50516001600160a01b031633146142eb57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b806142f581616372565b151560011461433857604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526143d293909290918301828280156143c55780601f1061439a576101008083540402835291602001916143c5565b820191906000526020600020905b8154815290600101906020018083116143a857829003601f168201915b5050505050836004617ec6565b156132b25760008054604051600160e01b631d09dc930281526020600482019081526024820188905283926001600160a01b031691631d09dc93918a918a91908190604401848480828437600081840152601f19601f82011690508083019250505093505050506040805180830381600087803b15801561445257600080fd5b505af1158015614466573d6000803e3d6000fd5b505050506040513d604081101561447c57600080fd5b5080516020909101519092509050811561452a5760068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261452a939092909183018282801561451d5780601f106144f25761010080835404028352916020019161451d565b820191906000526020600020905b81548152906001019060200180831161450057829003601f168201915b5050505050826000617fc0565b6000805460408051600160e01b63c214e5e50281526001600160a01b03898116602483015260048201928352604482018b90529092169163c214e5e5918b918b918b918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b1580156145af57600080fd5b505af11580156145c3573d6000803e3d6000fd5b505050506040513d60208110156145d957600080fd5b50519050801561243e5760068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261243e93909290918301828280156146705780601f1061464557610100808354040283529160200191614670565b820191906000526020600020905b81548152906001019060200180831161465357829003601f168201915b5050505050876001617fc0565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156146cb57600080fd5b505afa1580156146df573d6000803e3d6000fd5b505050506040513d60208110156146f557600080fd5b50516001600160a01b0316331461474057604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b808361474c8282615059565b151560011461478f57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b8461479981617d12565b15156001146147e05760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b6147ea8787613ef1565b15156001146148435760408051600160e51b62461bcd02815260206004820152601d60248201527f6f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b61484d8587618156565b15156001146148a65760408051600160e51b62461bcd02815260206004820152601460248201527f726f6c6520646f6573206e6f7420657869737473000000000000000000000000604482015290519081900360640190fd5b6001546000906001600160a01b031663be322e5487896148c581618171565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b83811015614919578181015183820152602001614901565b50505050905090810190601f1680156149465780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b83811015614979578181015183820152602001614961565b50505050905090810190601f1680156149a65780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156149d95781810151838201526020016149c1565b50505050905090810190601f168015614a065780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b158015614a2757600080fd5b505afa158015614a3b573d6000803e3d6000fd5b505050506040513d6020811015614a5157600080fd5b505160008054604051600160e21b63050e95810281526001600160a01b038c81166004830190815285151560648401526080602484019081528d5160848501528d51969750919093169463143a5604948e948e948e948a9492939092604483019260a401916020890191908190849084905b83811015614adb578181015183820152602001614ac3565b50505050905090810190601f168015614b085780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838360005b83811015614b3b578181015183820152602001614b23565b50505050905090810190601f168015614b685780820380516001836020036101000a031916815260200191505b509650505050505050600060405180830381600087803b158015612dec57600080fd5b600a5460009060ff161515614ba25750600161504e565b60005460408051600160e11b637ea7d02d0281526001600160a01b038b811660048301529151919092169163fd4fa05a916024808301926020929190829003018186803b158015614bf257600080fd5b505afa158015614c06573d6000803e3d6000fd5b505050506040513d6020811015614c1c57600080fd5b50516002141561504a576000805460408051600160e01b636acee5fd0281526001600160a01b038c81166004830152915160609485949390931692636acee5fd9260248082019391829003018186803b158015614c7857600080fd5b505afa158015614c8c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015614cb557600080fd5b810190808051600160201b811115614ccc57600080fd5b82016020810184811115614cdf57600080fd5b8151600160201b811182820187101715614cf857600080fd5b50509291906020018051600160201b811115614d1357600080fd5b82016020810184811115614d2657600080fd5b8151600160201b811182820187101715614d3f57600080fd5b5050929190505050915091506060614d5683618171565b60048054604051600160e01b633fd62ae702815260209281018381528751602483015287519495506001600160a01b0390921693633fd62ae793889392839260449091019185019080838360005b83811015614dbc578181015183820152602001614da4565b50505050905090810190601f168015614de95780820380516001836020036101000a031916815260200191505b509250505060206040518083038186803b158015614e0657600080fd5b505afa158015614e1a573d6000803e3d6000fd5b505050506040513d6020811015614e3057600080fd5b50511561504657614e408b616372565b80614e505750614e508b84615059565b15614e61576001935050505061504e565b60016001600160a01b038b161515614e7b57506002614e85565b8515614e85575060035b600154604051600160e11b6368fbbc33028152606481018390526080600482019081528551608483015285516001600160a01b039093169263d1f778669287928992889288929182916024810191604482019160a4019060208a019080838360005b83811015614eff578181015183820152602001614ee7565b50505050905090810190601f168015614f2c5780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b83811015614f5f578181015183820152602001614f47565b50505050905090810190601f168015614f8c5780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b83811015614fbf578181015183820152602001614fa7565b50505050905090810190601f168015614fec5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038186803b15801561500e57600080fd5b505afa158015615022573d6000803e3d6000fd5b505050506040513d602081101561503857600080fd5b5051945061504e9350505050565b5050505b5060005b979650505050505050565b600080546001600160a01b031663e8b42bf4848461507681618171565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b031681526020018060200180602001838103835285818151815260200191508051906020019080838360005b838110156150de5781810151838201526020016150c6565b50505050905090810190601f16801561510b5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561513e578181015183820152602001615126565b50505050905090810190601f16801561516b5780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038186803b15801561518b57600080fd5b505afa15801561519f573d6000803e3d6000fd5b505050506040513d60208110156151b557600080fd5b5051156151c457506001613fd9565b6001546000805460408051600160e01b6381d66b230281526001600160a01b03888116600483015291519482169463be322e549493909216926381d66b2392602480840193829003018186803b15801561521d57600080fd5b505afa158015615231573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561525a57600080fd5b810190808051600160201b81111561527157600080fd5b8201602081018481111561528457600080fd5b8151600160201b81118282018710171561529d57600080fd5b5050929190505050846152af86618171565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b838110156153035781810151838201526020016152eb565b50505050905090810190601f1680156153305780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b8381101561536357818101518382015260200161534b565b50505050905090810190601f1680156153905780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156153c35781810151838201526020016153ab565b50505050905090810190601f1680156153f05780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b158015613faa57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561545f57600080fd5b505afa158015615473573d6000803e3d6000fd5b505050506040513d602081101561548957600080fd5b50516001600160a01b031633146154d457604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b806154de81616372565b151560011461552157604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526155bb93909290918301828280156155ae5780601f10615583576101008083540402835291602001916155ae565b820191906000526020600020905b81548152906001019060200180831161559157829003601f168201915b5050505050836005617ec6565b1561577457600360009054906101000a90046001600160a01b03166001600160a01b03166337d50b27878787878c60056040518763ffffffff1660e01b81526004018080602001806020018761ffff1661ffff1681526020018661ffff1661ffff1681526020018060200185815260200184810384528a818151815260200191508051906020019080838360005b83811015615661578181015183820152602001615649565b50505050905090810190601f16801561568e5780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b838110156156c15781810151838201526020016156a9565b50505050905090810190601f1680156156ee5780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b83811015615721578181015183820152602001615709565b50505050905090810190601f16801561574e5780820380516001836020036101000a031916815260200191505b509950505050505050505050600060405180830381600087803b15801561202657600080fd5b50505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156157cb57600080fd5b505afa1580156157df573d6000803e3d6000fd5b505050506040513d60208110156157f557600080fd5b50516001600160a01b0316331461584057604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff161561588c5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b60048054604051600160e01b639e58eb9f028152602481018690526044810185905260609281019283526006805460026000196001831615610100020190911604606483018190526001600160a01b0390931693639e58eb9f93919288928892918291608490910190869080156159445780601f1061591957610100808354040283529160200191615944565b820191906000526020600020905b81548152906001019060200180831161592757829003601f168201915b5050945050505050600060405180830381600087803b15801561596657600080fd5b505af115801561597a573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526007805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b71357996509460069490928392918291602481019160c49091019089908015615a475780601f10615a1c57610100808354040283529160200191615a47565b820191906000526020600020905b815481529060010190602001808311615a2a57829003601f168201915b5050838103825287546002600019610100600184161502019091160480825260209091019088908015615abb5780601f10615a9057610100808354040283529160200191615abb565b820191906000526020600020905b815481529060010190602001808311615a9e57829003601f168201915b5050975050505050505050600060405180830381600087803b158015615ae057600080fd5b505af1158015615af4573d6000803e3d6000fd5b505060005460408051600160e01b63cef7f6af028152600481019182526007805460026000196001831615610100020190911604604483018190526001600160a01b03909416955063cef7f6af94509260089291829160248201916064019086908015615ba25780601f10615b7757610100808354040283529160200191615ba2565b820191906000526020600020905b815481529060010190602001808311615b8557829003601f168201915b5050838103825284546002600019610100600184161502019091160480825260209091019085908015615c165780601f10615beb57610100808354040283529160200191615c16565b820191906000526020600020905b815481529060010190602001808311615bf957829003601f168201915b5050945050505050600060405180830381600087803b158015615c3857600080fd5b505af1158015615774573d6000803e3d6000fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015615c9a57600080fd5b505afa158015615cae573d6000803e3d6000fd5b505050506040513d6020811015615cc457600080fd5b50516001600160a01b03163314615d0f57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b80615d1981616372565b1515600114615d5c57604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b8260011480615d6b5750826002145b1515615dc15760408051600160e51b62461bcd02815260206004820152601560248201527f4f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b6000808460011415615dd95750600290506003615dea565b8460021415615dea57506003905060055b615e2b87878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508592506182ad915050565b1515600114615e845760408051600160e51b62461bcd02815260206004820152601560248201527f6f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152615f1d9390929091830182828015615f115780601f10615ee657610100808354040283529160200191615f11565b820191906000526020600020905b815481529060010190602001808311615ef457829003601f168201915b50505050508584617ec6565b15615774576004805460408051600160e01b6314f775f902815260248101899052928301908152604483018990526001600160a01b03909116916314f775f9918a918a918a918190606401858580828437600081840152601f19601f820116905080830192505050945050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015615ff357600080fd5b505afa158015616007573d6000803e3d6000fd5b505050506040513d602081101561601d57600080fd5b50516001600160a01b0316331461606857604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b6160728188615059565b15156001146160b557604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b81600114806160c45750816002145b806160cf5750816003145b151561610f57604051600160e51b62461bcd0281526004018080602001828103825260258152602001806183e96025913960400191505060405180910390fd5b600354604051600160e01b6337d50b2702815261ffff80871660448301528516606482015260a4810184905260c060048201908152885160c483015288516001600160a01b03909316926337d50b27928a928a928a928a928f928b9282916024820191608481019160e49091019060208c0190808383600083811015615661578181015183820152602001615649565b600a5460068054604080516020601f6002600019600187161561010002019095169490940493840181900481028201810190925282815260609485948594600094919360079360089360ff9091169286918301828280156162415780601f1061621657610100808354040283529160200191616241565b820191906000526020600020905b81548152906001019060200180831161622457829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156162cf5780601f106162a4576101008083540402835291602001916162cf565b820191906000526020600020905b8154815290600101906020018083116162b257829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529598508794509250840190508282801561635d5780601f106163325761010080835404028352916020019161635d565b820191906000526020600020905b81548152906001019060200180831161634057829003601f168201915b50505050509150935093509350935090919293565b604080516020808201908152600780546002600019610100600184161502019091160493830184905260009390928291606090910190849080156163f75780601f106163cc576101008083540402835291602001916163f7565b820191906000526020600020905b8154815290600101906020018083116163da57829003601f168201915b505060408051601f19818403018152828252805160209091012060008054600160e01b6381d66b230285526001600160a01b038a8116600487015293519297509290921694506381d66b239350602480840193829003018186803b15801561645e57600080fd5b505afa158015616472573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561649b57600080fd5b810190808051600160201b8111156164b257600080fd5b820160208101848111156164c557600080fd5b8151600160201b8111828201871017156164de57600080fd5b50509291905050506040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561652657818101518382015260200161650e565b50505050905090810190601f1680156165535780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120149050919050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156165c457600080fd5b505afa1580156165d8573d6000803e3d6000fd5b505050506040513d60208110156165ee57600080fd5b50516001600160a01b0316331461663957604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061664381616372565b151560011461668657604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b600360009054906101000a90046001600160a01b03166001600160a01b03166337d50b27878787878c60046040518763ffffffff1660e01b81526004018080602001806020018761ffff1661ffff1681526020018661ffff1661ffff1681526020018060200185815260200184810384528a818151815260200191508051906020019080838360005b8381101561672757818101518382015260200161670f565b50505050905090810190601f1680156167545780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b8381101561678757818101518382015260200161676f565b50505050905090810190601f1680156167b45780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b838110156167e75781810151838201526020016167cf565b50505050905090810190601f1680156168145780820380516001836020036101000a031916815260200191505b509950505050505050505050600060405180830381600087803b15801561683a57600080fd5b505af115801561684e573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d02815260006064820181905260056084830181905260a0600484019081526006805460001960018216156101000201169690960460a485018190526001600160a01b03909516975063e98ac22d96508e948e9482916024820191604481019160c4909101908a9080156169155780601f106168ea57610100808354040283529160200191616915565b820191906000526020600020905b8154815290600101906020018083116168f857829003601f168201915b505084810383528851815288516020918201918a019080838360005b83811015616949578181015183820152602001616931565b50505050905090810190601f1680156169765780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b838110156169a9578181015183820152602001616991565b50505050905090810190601f1680156169d65780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015616a4957600080fd5b505afa158015616a5d573d6000803e3d6000fd5b505050506040513d6020811015616a7357600080fd5b50516001600160a01b03163314616abe57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460ff161515600114616b0b5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b616b1481616372565b1515600114616b5757604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60028054604051600160e01b63e98ac22d0281526001600160a01b03858116606483015260016084830181905260a06004840190815260068054600019818516156101000201169690960460a48501819052929094169463e98ac22d9490938d938d938a939092909182916024810191604482019160c401908a908015616c1f5780601f10616bf457610100808354040283529160200191616c1f565b820191906000526020600020905b815481529060010190602001808311616c0257829003601f168201915b505084810383528851815288516020918201918a019080838360005b83811015616c53578181015183820152602001616c3b565b50505050905090810190601f168015616c805780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b83811015616cb3578181015183820152602001616c9b565b50505050905090810190601f168015616ce05780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b158015616d0557600080fd5b505af1158015616d19573d6000803e3d6000fd5b505060048054604051600160e01b63f9953de502815260209281018381528c5160248301528c516001600160a01b03909316955063f9953de594508c93909283926044019185019080838360005b83811015616d7f578181015183820152602001616d67565b50505050905090810190601f168015616dac5780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b158015616dcb57600080fd5b505af1158015616ddf573d6000803e3d6000fd5b5050600354604051600160e01b63549583df02815261ffff80891660448301528716606482015260a0600482019081528a5160a48301528a516001600160a01b03909316945063549583df93508a928a928a928a928f9282916024820191608481019160c49091019060208b019080838360005b83811015616e6b578181015183820152602001616e53565b50505050905090810190601f168015616e985780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015616ecb578181015183820152602001616eb3565b50505050905090810190601f168015616ef85780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015616f2b578181015183820152602001616f13565b50505050905090810190601f168015616f585780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b158015616f7d57600080fd5b505af1158015616f91573d6000803e3d6000fd5b50505050616f9f8288613ef1565b1515600114616ff85760408051600160e51b62461bcd02815260206004820152601d60248201527f4f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b60008054604051600160e01b63e3483a9d0281526001600160a01b03858116600483019081526001606484018190526080602485019081528d5160848601528d51939095169563e3483a9d9589958f9560089593604483019260a4019160208901918190849084905b83811015617079578181015183820152602001617061565b50505050905090810190601f1680156170a65780820380516001836020036101000a031916815260200191505b508381038252855460026000196101006001841615020190911604808252602090910190869080156171195780601f106170ee57610100808354040283529160200191617119565b820191906000526020600020905b8154815290600101906020018083116170fc57829003601f168201915b50509650505050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561718b57600080fd5b505afa15801561719f573d6000803e3d6000fd5b505050506040513d60208110156171b557600080fd5b50516001600160a01b0316331461720057604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8561720a81617d12565b15156001146172515760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b61725b8288615059565b151560011461729e57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b600354604051600160e01b634c57331102815261ffff80871660448301528516606482015260a060048201908152885160a483015288516001600160a01b0390931692634c573311928a928a928a928a928f92909182916024820191608481019160c49091019060208b019080838360005b83811015617328578181015183820152602001617310565b50505050905090810190601f1680156173555780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015617388578181015183820152602001617370565b50505050905090810190601f1680156173b55780820380516001836020036101000a031916815260200191505b5084810382528551815285516020918201918701908083836000838110156169a9578181015183820152602001616991565b600254604051600160e21b62539ab302815260206004820190815260248201849052606092839260009283926001600160a01b03169163014e6acc9189918991908190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561746757600080fd5b505afa15801561747b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156174a457600080fd5b810190808051600160201b8111156174bb57600080fd5b820160208101848111156174ce57600080fd5b8151600160201b8111828201871017156174e757600080fd5b50509291906020018051600160201b81111561750257600080fd5b8201602081018481111561751557600080fd5b8151600160201b81118282018710171561752e57600080fd5b50506020820151604090920151949b909a5090985092965091945050505050565b6005546001600160a01b031633146175b15760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600a5460009060ff16156175fd5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b6176096006898961831a565b506176166007878761831a565b506176236008858561831a565b5050600a805460ff1916911515919091179055505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561768c57600080fd5b505afa1580156176a0573d6000803e3d6000fd5b505050506040513d60208110156176b657600080fd5b50516001600160a01b0316331461770157604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b61770a81616372565b151560011461774d57604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b6177588760016182ad565b15156001146177b15760408051600160e51b62461bcd02815260206004820152601260248201527f4e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261784b939092909183018282801561783e5780601f106178135761010080835404028352916020019161783e565b820191906000526020600020905b81548152906001019060200180831161782157829003601f168201915b5050505050826001617ec6565b156157745760048054604051600160e11b637181418b02815260209281018381528a5160248301528a516001600160a01b039093169363e3028316938c9383926044909101919085019080838360005b838110156178b357818101518382015260200161789b565b50505050905090810190601f1680156178e05780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b1580156178ff57600080fd5b505af1158015617913573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526008805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b7135799650948e9490928392918291602481019160c490910190899080156179df5780601f106179b4576101008083540402835291602001916179df565b820191906000526020600020905b8154815290600101906020018083116179c257829003601f168201915b5050838103825287518152875160209182019189019080838360005b83811015617a135781810151838201526020016179fb565b50505050905090810190601f168015617a405780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b158015617a6457600080fd5b505af1158015617a78573d6000803e3d6000fd5b5050600354604051600160e21b633e0b822b02815261ffff80891660448301528716606482015260a0600482019081528a5160a48301528a516001600160a01b03909316945063f82e08ac93508a928a928a928a928f9282916024820191608481019160c49091019060208b019080838360005b83811015617b04578181015183820152602001617aec565b50505050905090810190601f168015617b315780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015617b64578181015183820152602001617b4c565b50505050905090810190601f168015617b915780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015617bc4578181015183820152602001617bac565b50505050905090810190601f168015617bf15780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b158015617c1657600080fd5b505af1158015617c2a573d6000803e3d6000fd5b50506000805460408051600160e01b63c214e5e50281526001600160a01b038881166024830152600482019283528d5160448301528d519316955063c214e5e594508c9388938392606401916020870191908190849084905b83811015617c9b578181015183820152602001617c83565b50505050905090810190601f168015617cc85780820380516001836020036101000a031916815260200191505b509350505050602060405180830381600087803b158015617ce857600080fd5b505af1158015617cfc573d6000803e3d6000fd5b505050506040513d602081101561273c57600080fd5b6004805460408051600160e01b638c8642df0281526002602482018190529381019182528451604482015284516000946001600160a01b0390941693638c8642df93879391929091829160649091019060208601908083838c5b83811015617d84578181015183820152602001617d6c565b50505050905090810190601f168015617db15780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015617dcf57600080fd5b505afa158015617de3573d6000803e3d6000fd5b505050506040513d6020811015617df957600080fd5b505192915050565b600480546040517fffe40d1d00000000000000000000000000000000000000000000000000000000815260209281018381528451602483015284516000946001600160a01b039094169363ffe40d1d9387939283926044909201918501908083838b5b83811015617e7c578181015183820152602001617e64565b50505050905090810190601f168015617ea95780820380516001836020036101000a031916815260200191505b509250505060206040518083038186803b158015617dcf57600080fd5b600254604051600160e21b632c084e190281526001600160a01b03848116602483015260448201849052606060048301908152865160648401528651600094929092169263b02138649288928892889282916084019060208701908083838d5b83811015617f3e578181015183820152602001617f26565b50505050905090810190601f168015617f6b5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015617f8c57600080fd5b505af1158015617fa0573d6000803e3d6000fd5b505050506040513d6020811015617fb657600080fd5b5051949350505050565b801561809a5760025460408051600160e01b635607395b0281526001600160a01b03858116602483015260048201928352865160448301528651931692635607395b9287928792829160640190602086019080838360005b83811015618030578181015183820152602001618018565b50505050905090810190601f16801561805d5780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b15801561807d57600080fd5b505af1158015618091573d6000803e3d6000fd5b50505050618151565b60025460408051600160e11b632ce5eb7f0281526001600160a01b038581166024830152600482019283528651604483015286519316926359cbd6fe9287928792829160640190602086019080838360005b838110156181045781810151838201526020016180ec565b50505050905090810190601f1680156181315780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b158015615c3857600080fd5b505050565b6001546000906001600160a01b031663abf5739f84846152af815b60048054604051600160e11b630bbe46c502815260209281018381528451602483015284516060946001600160a01b039094169363177c8d8a93879392839260449092019185019080838360005b838110156181d75781810151838201526020016181bf565b50505050905090810190601f1680156182045780820380516001836020036101000a031916815260200191505b509250505060006040518083038186803b15801561822157600080fd5b505afa158015618235573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561825e57600080fd5b810190808051600160201b81111561827557600080fd5b8201602081018481111561828857600080fd5b8151600160201b8111828201871017156182a157600080fd5b50909695505050505050565b6004805460408051600160e01b638c8642df028152602481018590529283019081528451604484015284516000936001600160a01b0390931692638c8642df9287928792829160649091019060208601908083838c83811015613f5f578181015183820152602001613f47565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061835b5782800160ff19823516178555618388565b82800160010185558215618388579182015b8281111561838857823582559160200191906001019061836d565b50612f68926132c09250905b80821115612f68576000815560010161839456fe6f7267206e6f7420696e20617070726f76656420737461747573000000000000496e636f7272656374206e6574776f726b20626f6f7420737461747573000000696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f77656463616e2062652063616c6c656420627920696e7465726661636520636f6e7472616374206f6e6c796163636f756e74206973206e6f742061206e6574776f726b2061646d696e206163636f756e746163636f756e74206973206e6f742061206f72672061646d696e206163636f756e74a165627a7a72305820076e128f2df9e9b08b731798802e430db87855f1ea7dd2b592784c23ef6897aa0029" + +// DeployPermImpl deploys a new Ethereum contract, binding an instance of PermImpl to it. +func DeployPermImpl(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address, _orgManager common.Address, _rolesManager common.Address, _accountManager common.Address, _voterManager common.Address, _nodeManager common.Address) (common.Address, *types.Transaction, *PermImpl, error) { + parsed, err := abi.JSON(strings.NewReader(PermImplABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PermImplBin), backend, _permUpgradable, _orgManager, _rolesManager, _accountManager, _voterManager, _nodeManager) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &PermImpl{PermImplCaller: PermImplCaller{contract: contract}, PermImplTransactor: PermImplTransactor{contract: contract}, PermImplFilterer: PermImplFilterer{contract: contract}}, nil +} + +// PermImpl is an auto generated Go binding around an Ethereum contract. +type PermImpl struct { + PermImplCaller // Read-only binding to the contract + PermImplTransactor // Write-only binding to the contract + PermImplFilterer // Log filterer for contract events +} + +// PermImplCaller is an auto generated read-only Go binding around an Ethereum contract. +type PermImplCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermImplTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PermImplTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermImplFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PermImplFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermImplSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PermImplSession struct { + Contract *PermImpl // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermImplCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PermImplCallerSession struct { + Contract *PermImplCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PermImplTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PermImplTransactorSession struct { + Contract *PermImplTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermImplRaw is an auto generated low-level Go binding around an Ethereum contract. +type PermImplRaw struct { + Contract *PermImpl // Generic contract binding to access the raw methods on +} + +// PermImplCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PermImplCallerRaw struct { + Contract *PermImplCaller // Generic read-only contract binding to access the raw methods on +} + +// PermImplTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PermImplTransactorRaw struct { + Contract *PermImplTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPermImpl creates a new instance of PermImpl, bound to a specific deployed contract. +func NewPermImpl(address common.Address, backend bind.ContractBackend) (*PermImpl, error) { + contract, err := bindPermImpl(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PermImpl{PermImplCaller: PermImplCaller{contract: contract}, PermImplTransactor: PermImplTransactor{contract: contract}, PermImplFilterer: PermImplFilterer{contract: contract}}, nil +} + +// NewPermImplCaller creates a new read-only instance of PermImpl, bound to a specific deployed contract. +func NewPermImplCaller(address common.Address, caller bind.ContractCaller) (*PermImplCaller, error) { + contract, err := bindPermImpl(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PermImplCaller{contract: contract}, nil +} + +// NewPermImplTransactor creates a new write-only instance of PermImpl, bound to a specific deployed contract. +func NewPermImplTransactor(address common.Address, transactor bind.ContractTransactor) (*PermImplTransactor, error) { + contract, err := bindPermImpl(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PermImplTransactor{contract: contract}, nil +} + +// NewPermImplFilterer creates a new log filterer instance of PermImpl, bound to a specific deployed contract. +func NewPermImplFilterer(address common.Address, filterer bind.ContractFilterer) (*PermImplFilterer, error) { + contract, err := bindPermImpl(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PermImplFilterer{contract: contract}, nil +} + +// bindPermImpl binds a generic wrapper to an already deployed contract. +func bindPermImpl(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PermImplABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermImpl *PermImplRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermImpl.Contract.PermImplCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermImpl *PermImplRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermImpl.Contract.PermImplTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermImpl *PermImplRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermImpl.Contract.PermImplTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermImpl *PermImplCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermImpl.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermImpl *PermImplTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermImpl.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermImpl *PermImplTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermImpl.Contract.contract.Transact(opts, method, params...) +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_PermImpl *PermImplCaller) ConnectionAllowed(opts *bind.CallOpts, _enodeId string, _ip string, _port uint16) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "connectionAllowed", _enodeId, _ip, _port) + return *ret0, err +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_PermImpl *PermImplSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { + return _PermImpl.Contract.ConnectionAllowed(&_PermImpl.CallOpts, _enodeId, _ip, _port) +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_PermImpl *PermImplCallerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { + return _PermImpl.Contract.ConnectionAllowed(&_PermImpl.CallOpts, _enodeId, _ip, _port) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermImpl *PermImplCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "getNetworkBootStatus") + return *ret0, err +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermImpl *PermImplSession) GetNetworkBootStatus() (bool, error) { + return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermImpl *PermImplCallerSession) GetNetworkBootStatus() (bool, error) { + return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermImpl *PermImplCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(common.Address) + ret3 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _PermImpl.contract.Call(opts, out, "getPendingOp", _orgId) + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermImpl *PermImplSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermImpl *PermImplCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) +} + +// GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. +// +// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +func (_PermImpl *PermImplCaller) GetPolicyDetails(opts *bind.CallOpts) (string, string, string, bool, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(string) + ret3 = new(bool) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _PermImpl.contract.Call(opts, out, "getPolicyDetails") + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. +// +// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +func (_PermImpl *PermImplSession) GetPolicyDetails() (string, string, string, bool, error) { + return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) +} + +// GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. +// +// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +func (_PermImpl *PermImplCallerSession) GetPolicyDetails() (string, string, string, bool, error) { + return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermImpl *PermImplCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "isNetworkAdmin", _account) + return *ret0, err +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermImpl *PermImplSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermImpl *PermImplCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) + return *ret0, err +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. +// +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +func (_PermImpl *PermImplCaller) TransactionAllowed(opts *bind.CallOpts, _sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "transactionAllowed", _sender, _target, _value, _gasPrice, _gasLimit, _payload) + return *ret0, err +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. +// +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +func (_PermImpl *PermImplSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { + return _PermImpl.Contract.TransactionAllowed(&_PermImpl.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. +// +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +func (_PermImpl *PermImplCallerSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { + return _PermImpl.Contract.TransactionAllowed(&_PermImpl.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermImpl.contract.Call(opts, out, "validateAccount", _account, _orgId) + return *ret0, err +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermImpl *PermImplCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _account) returns() +func (_PermImpl *PermImplTransactor) AddAdminAccount(opts *bind.TransactOpts, _account common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addAdminAccount", _account) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _account) returns() +func (_PermImpl *PermImplSession) AddAdminAccount(_account common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminAccount(&_PermImpl.TransactOpts, _account) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _account) returns() +func (_PermImpl *PermImplTransactorSession) AddAdminAccount(_account common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminAccount(&_PermImpl.TransactOpts, _account) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x8683c7fe. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermImpl *PermImplTransactor) AddAdminNode(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addAdminNode", _enodeId, _ip, _port, _raftport) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x8683c7fe. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermImpl *PermImplSession) AddAdminNode(_enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminNode(&_PermImpl.TransactOpts, _enodeId, _ip, _port, _raftport) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x8683c7fe. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermImpl *PermImplTransactorSession) AddAdminNode(_enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermImpl.Contract.AddAdminNode(&_PermImpl.TransactOpts, _enodeId, _ip, _port, _raftport) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x1b04c276. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddNewRole(opts *bind.TransactOpts, _roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addNewRole", _roleId, _orgId, _access, _voter, _admin, _caller) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x1b04c276. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin, address _caller) returns() +func (_PermImpl *PermImplSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNewRole(&_PermImpl.TransactOpts, _roleId, _orgId, _access, _voter, _admin, _caller) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x1b04c276. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNewRole(&_PermImpl.TransactOpts, _roleId, _orgId, _access, _voter, _admin, _caller) +} + +// AddNode is a paid mutator transaction binding the contract method 0xecad01d5. +// +// Solidity: function addNode(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddNode(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addNode", _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// AddNode is a paid mutator transaction binding the contract method 0xecad01d5. +// +// Solidity: function addNode(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplSession) AddNode(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNode(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// AddNode is a paid mutator transaction binding the contract method 0xecad01d5. +// +// Solidity: function addNode(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddNode(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddNode(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xe91b0e19. +// +// Solidity: function addOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addOrg", _orgId, _enodeId, _ip, _port, _raftport, _account, _caller) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xe91b0e19. +// +// Solidity: function addOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) AddOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account, _caller) +} + +// AddOrg is a paid mutator transaction binding the contract method 0xe91b0e19. +// +// Solidity: function addOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account, _caller) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x68a61273. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactor) AddSubOrg(opts *bind.TransactOpts, _pOrgId string, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "addSubOrg", _pOrgId, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x68a61273. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddSubOrg(&_PermImpl.TransactOpts, _pOrgId, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x68a61273. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AddSubOrg(&_PermImpl.TransactOpts, _pOrgId, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x88843041. +// +// Solidity: function approveAdminRole(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveAdminRole", _orgId, _account, _caller) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x88843041. +// +// Solidity: function approveAdminRole(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveAdminRole(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x88843041. +// +// Solidity: function approveAdminRole(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveAdminRole(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x4b20f45f. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveBlacklistedAccountRecovery", _orgId, _account, _caller) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x4b20f45f. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x4b20f45f. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xa042bf40. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveBlacklistedNodeRecovery", _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xa042bf40. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xa042bf40. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xf75f0a06. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveOrg", _orgId, _enodeId, _ip, _port, _raftport, _account, _caller) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xf75f0a06. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account, _caller) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xf75f0a06. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrg(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account, _caller) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0xb5546564. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) ApproveOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "approveOrgStatus", _orgId, _action, _caller) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0xb5546564. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) ApproveOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0xb5546564. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) ApproveOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.ApproveOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x8baa8191. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactor) AssignAccountRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "assignAccountRole", _account, _orgId, _roleId, _caller) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x8baa8191. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, address _caller) returns() +func (_PermImpl *PermImplSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAccountRole(&_PermImpl.TransactOpts, _account, _orgId, _roleId, _caller) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x8baa8191. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAccountRole(&_PermImpl.TransactOpts, _account, _orgId, _roleId, _caller) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x404bf3eb. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactor) AssignAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "assignAdminRole", _orgId, _account, _roleId, _caller) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x404bf3eb. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId, address _caller) returns() +func (_PermImpl *PermImplSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _roleId, _caller) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x404bf3eb. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.AssignAdminRole(&_PermImpl.TransactOpts, _orgId, _account, _roleId, _caller) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermImpl *PermImplTransactor) Init(opts *bind.TransactOpts, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "init", _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermImpl *PermImplSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermImpl.Contract.Init(&_PermImpl.TransactOpts, _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermImpl *PermImplTransactorSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermImpl.Contract.Init(&_PermImpl.TransactOpts, _breadth, _depth) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0x5ca5adbe. +// +// Solidity: function removeRole(string _roleId, string _orgId, address _caller) returns() +func (_PermImpl *PermImplTransactor) RemoveRole(opts *bind.TransactOpts, _roleId string, _orgId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "removeRole", _roleId, _orgId, _caller) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0x5ca5adbe. +// +// Solidity: function removeRole(string _roleId, string _orgId, address _caller) returns() +func (_PermImpl *PermImplSession) RemoveRole(_roleId string, _orgId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.RemoveRole(&_PermImpl.TransactOpts, _roleId, _orgId, _caller) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0x5ca5adbe. +// +// Solidity: function removeRole(string _roleId, string _orgId, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) RemoveRole(_roleId string, _orgId string, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.RemoveRole(&_PermImpl.TransactOpts, _roleId, _orgId, _caller) +} + +// SetMigrationPolicy is a paid mutator transaction binding the contract method 0xf5ad584a. +// +// Solidity: function setMigrationPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole, bool _networkBootStatus) returns() +func (_PermImpl *PermImplTransactor) SetMigrationPolicy(opts *bind.TransactOpts, _nwAdminOrg string, _nwAdminRole string, _oAdminRole string, _networkBootStatus bool) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "setMigrationPolicy", _nwAdminOrg, _nwAdminRole, _oAdminRole, _networkBootStatus) +} + +// SetMigrationPolicy is a paid mutator transaction binding the contract method 0xf5ad584a. +// +// Solidity: function setMigrationPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole, bool _networkBootStatus) returns() +func (_PermImpl *PermImplSession) SetMigrationPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string, _networkBootStatus bool) (*types.Transaction, error) { + return _PermImpl.Contract.SetMigrationPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole, _networkBootStatus) +} + +// SetMigrationPolicy is a paid mutator transaction binding the contract method 0xf5ad584a. +// +// Solidity: function setMigrationPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole, bool _networkBootStatus) returns() +func (_PermImpl *PermImplTransactorSession) SetMigrationPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string, _networkBootStatus bool) (*types.Transaction, error) { + return _PermImpl.Contract.SetMigrationPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole, _networkBootStatus) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermImpl *PermImplTransactor) SetPolicy(opts *bind.TransactOpts, _nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "setPolicy", _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermImpl *PermImplSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermImpl.Contract.SetPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermImpl *PermImplTransactorSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermImpl.Contract.SetPolicy(&_PermImpl.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x1c249912. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactor) StartBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "startBlacklistedAccountRecovery", _orgId, _account, _caller) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x1c249912. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x1c249912. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedAccountRecovery(&_PermImpl.TransactOpts, _orgId, _account, _caller) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xd621d957. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactor) StartBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "startBlacklistedNodeRecovery", _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xd621d957. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0xd621d957. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.StartBlacklistedNodeRecovery(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _caller) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x04e81f1e. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) UpdateAccountStatus(opts *bind.TransactOpts, _orgId string, _account common.Address, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateAccountStatus", _orgId, _account, _action, _caller) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x04e81f1e. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateAccountStatus(&_PermImpl.TransactOpts, _orgId, _account, _action, _caller) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x04e81f1e. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateAccountStatus(&_PermImpl.TransactOpts, _orgId, _account, _action, _caller) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermImpl *PermImplTransactor) UpdateNetworkBootStatus(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateNetworkBootStatus") +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermImpl *PermImplSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNetworkBootStatus(&_PermImpl.TransactOpts) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermImpl *PermImplTransactorSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNetworkBootStatus(&_PermImpl.TransactOpts) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xb9b7fe6c. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) UpdateNodeStatus(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateNodeStatus", _orgId, _enodeId, _ip, _port, _raftport, _action, _caller) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xb9b7fe6c. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) UpdateNodeStatus(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNodeStatus(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _action, _caller) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xb9b7fe6c. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) UpdateNodeStatus(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateNodeStatus(&_PermImpl.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _action, _caller) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0x3cf5f33b. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactor) UpdateOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.contract.Transact(opts, "updateOrgStatus", _orgId, _action, _caller) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0x3cf5f33b. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplSession) UpdateOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0x3cf5f33b. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action, address _caller) returns() +func (_PermImpl *PermImplTransactorSession) UpdateOrgStatus(_orgId string, _action *big.Int, _caller common.Address) (*types.Transaction, error) { + return _PermImpl.Contract.UpdateOrgStatus(&_PermImpl.TransactOpts, _orgId, _action, _caller) +} + +// PermImplPermissionsInitializedIterator is returned from FilterPermissionsInitialized and is used to iterate over the raw logs and unpacked data for PermissionsInitialized events raised by the PermImpl contract. +type PermImplPermissionsInitializedIterator struct { + Event *PermImplPermissionsInitialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PermImplPermissionsInitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PermImplPermissionsInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PermImplPermissionsInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PermImplPermissionsInitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PermImplPermissionsInitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PermImplPermissionsInitialized represents a PermissionsInitialized event raised by the PermImpl contract. +type PermImplPermissionsInitialized struct { + NetworkBootStatus bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterPermissionsInitialized is a free log retrieval operation binding the contract event 0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf. +// +// Solidity: event PermissionsInitialized(bool _networkBootStatus) +func (_PermImpl *PermImplFilterer) FilterPermissionsInitialized(opts *bind.FilterOpts) (*PermImplPermissionsInitializedIterator, error) { + + logs, sub, err := _PermImpl.contract.FilterLogs(opts, "PermissionsInitialized") + if err != nil { + return nil, err + } + return &PermImplPermissionsInitializedIterator{contract: _PermImpl.contract, event: "PermissionsInitialized", logs: logs, sub: sub}, nil +} + +var PermissionsInitializedTopicHash = "0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf" + +// WatchPermissionsInitialized is a free log subscription operation binding the contract event 0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf. +// +// Solidity: event PermissionsInitialized(bool _networkBootStatus) +func (_PermImpl *PermImplFilterer) WatchPermissionsInitialized(opts *bind.WatchOpts, sink chan<- *PermImplPermissionsInitialized) (event.Subscription, error) { + + logs, sub, err := _PermImpl.contract.WatchLogs(opts, "PermissionsInitialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PermImplPermissionsInitialized) + if err := _PermImpl.contract.UnpackLog(event, "PermissionsInitialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParsePermissionsInitialized is a log parse operation binding the contract event 0x04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf. +// +// Solidity: event PermissionsInitialized(bool _networkBootStatus) +func (_PermImpl *PermImplFilterer) ParsePermissionsInitialized(log types.Log) (*PermImplPermissionsInitialized, error) { + event := new(PermImplPermissionsInitialized) + if err := _PermImpl.contract.UnpackLog(event, "PermissionsInitialized", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v2/bind/permission_interface.go b/permission/v2/bind/permission_interface.go new file mode 100644 index 0000000000..3f0e032bcf --- /dev/null +++ b/permission/v2/bind/permission_interface.go @@ -0,0 +1,892 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PermInterfaceABI is the input ABI used to generate the binding from. +const PermInterfaceABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"getPermissionsImpl\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"approveAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_nwAdminOrg\",\"type\":\"string\"},{\"name\":\"_nwAdminRole\",\"type\":\"string\"},{\"name\":\"_oAdminRole\",\"type\":\"string\"}],\"name\":\"setPolicy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_pOrgId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"}],\"name\":\"addSubOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_roleId\",\"type\":\"string\"}],\"name\":\"assignAccountRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"approveBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateNodeStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_roleId\",\"type\":\"string\"}],\"name\":\"assignAdminRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updateNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"}],\"name\":\"connectionAllowed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNetworkBootStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_acct\",\"type\":\"address\"}],\"name\":\"addAdminAccount\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_permImplementation\",\"type\":\"address\"}],\"name\":\"setPermImplementation\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"addOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_access\",\"type\":\"uint256\"},{\"name\":\"_voter\",\"type\":\"bool\"},{\"name\":\"_admin\",\"type\":\"bool\"}],\"name\":\"addNewRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"}],\"name\":\"approveBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"approveOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"validateAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateAccountStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"}],\"name\":\"addAdminNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"}],\"name\":\"startBlacklistedNodeRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_sender\",\"type\":\"address\"},{\"name\":\"_target\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_gasPrice\",\"type\":\"uint256\"},{\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"name\":\"_payload\",\"type\":\"bytes\"}],\"name\":\"transactionAllowed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"isOrgAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_breadth\",\"type\":\"uint256\"},{\"name\":\"_depth\",\"type\":\"uint256\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"removeRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"startBlacklistedAccountRecovery\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_action\",\"type\":\"uint256\"}],\"name\":\"updateOrgStatus\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"isNetworkAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"}],\"name\":\"addNode\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getPendingOp\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_ip\",\"type\":\"string\"},{\"name\":\"_port\",\"type\":\"uint16\"},{\"name\":\"_raftport\",\"type\":\"uint16\"},{\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"approveOrg\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permImplUpgradeable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" + +var PermInterfaceParsedABI, _ = abi.JSON(strings.NewReader(PermInterfaceABI)) + +// PermInterfaceBin is the compiled bytecode used for deploying new contracts. +var PermInterfaceBin = "0x608060405234801561001057600080fd5b506040516020806134518339810180604052602081101561003057600080fd5b5051600280546001600160a01b0319166001600160a01b039092169190911790556133f1806100606000396000f3fe608060405234801561001057600080fd5b50600436106101e55760003560e01c806358dcff711161010f578063a5843f08116100a2578063d1aa0c2011610071578063d1aa0c2014611673578063ef5f719614611699578063f346a3a714611858578063fa279d61146119c4576101e5565b8063a5843f08146114ad578063a6343012146114d0578063a97914bf1461158e578063bb3b6e8014611605576101e5565b80638683c7fe116100de5780638683c7fe1461109c57806391ba3f96146111d6578063936421d5146113955780639bd381011461142f576101e5565b806358dcff7114610d745780635be9672c14610f335780636b568d7614610fa157806384b7a84a1461101f576101e5565b806343de646c116101875780634fe57e7a116101565780634fe57e7a14610a90578063511bbd9f14610ab6578063513a327714610adc57806351f604c314610ca6576101e5565b806343de646c146108df57806344478e79146109aa57806345a59e5b146109c65780634cbfa82e14610a88576101e5565b80632e125a6c116101c35780632e125a6c146103955780632f7f0a12146105d95780633e239b23146106a75780633f9be4971461071e576101e5565b806303ed6933146101ea57806316724c441461020e5780631b61022014610287575b600080fd5b6101f2611b8e565b604080516001600160a01b039092168252519081900360200190f35b6102856004803603604081101561022457600080fd5b810190602081018135600160201b81111561023e57600080fd5b82018360208201111561025057600080fd5b803590602001918460018302840111600160201b8311171561027157600080fd5b9193509150356001600160a01b0316611b9d565b005b6102856004803603606081101561029d57600080fd5b810190602081018135600160201b8111156102b757600080fd5b8201836020820111156102c957600080fd5b803590602001918460018302840111600160201b831117156102ea57600080fd5b919390929091602081019035600160201b81111561030757600080fd5b82018360208201111561031957600080fd5b803590602001918460018302840111600160201b8311171561033a57600080fd5b919390929091602081019035600160201b81111561035757600080fd5b82018360208201111561036957600080fd5b803590602001918460018302840111600160201b8311171561038a57600080fd5b509092509050611c4c565b610285600480360360c08110156103ab57600080fd5b810190602081018135600160201b8111156103c557600080fd5b8201836020820111156103d757600080fd5b803590602001918460018302840111600160201b831117156103f857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561044a57600080fd5b82018360208201111561045c57600080fd5b803590602001918460018302840111600160201b8311171561047d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156104cf57600080fd5b8201836020820111156104e157600080fd5b803590602001918460018302840111600160201b8311171561050257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561055457600080fd5b82018360208201111561056657600080fd5b803590602001918460018302840111600160201b8311171561058757600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff8335811694506020909301359092169150611d439050565b610285600480360360608110156105ef57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561061957600080fd5b82018360208201111561062b57600080fd5b803590602001918460018302840111600160201b8311171561064c57600080fd5b919390929091602081019035600160201b81111561066957600080fd5b82018360208201111561067b57600080fd5b803590602001918460018302840111600160201b8311171561069c57600080fd5b509092509050611f52565b610285600480360360408110156106bd57600080fd5b810190602081018135600160201b8111156106d757600080fd5b8201836020820111156106e957600080fd5b803590602001918460018302840111600160201b8311171561070a57600080fd5b9193509150356001600160a01b0316612033565b610285600480360360c081101561073457600080fd5b810190602081018135600160201b81111561074e57600080fd5b82018360208201111561076057600080fd5b803590602001918460018302840111600160201b8311171561078157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156107d357600080fd5b8201836020820111156107e557600080fd5b803590602001918460018302840111600160201b8311171561080657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561085857600080fd5b82018360208201111561086a57600080fd5b803590602001918460018302840111600160201b8311171561088b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff833581169450602084013516926040013591506120c59050565b610285600480360360608110156108f557600080fd5b810190602081018135600160201b81111561090f57600080fd5b82018360208201111561092157600080fd5b803590602001918460018302840111600160201b8311171561094257600080fd5b919390926001600160a01b0383351692604081019060200135600160201b81111561096c57600080fd5b82018360208201111561097e57600080fd5b803590602001918460018302840111600160201b8311171561099f57600080fd5b509092509050612274565b6109b2612333565b604080519115158252519081900360200190f35b6109b2600480360360608110156109dc57600080fd5b810190602081018135600160201b8111156109f657600080fd5b820183602082011115610a0857600080fd5b803590602001918460018302840111600160201b83111715610a2957600080fd5b919390929091602081019035600160201b811115610a4657600080fd5b820183602082011115610a5857600080fd5b803590602001918460018302840111600160201b83111715610a7957600080fd5b91935091503561ffff166123b5565b6109b26124a1565b61028560048036036020811015610aa657600080fd5b50356001600160a01b0316612504565b61028560048036036020811015610acc57600080fd5b50356001600160a01b031661256d565b610285600480360360c0811015610af257600080fd5b810190602081018135600160201b811115610b0c57600080fd5b820183602082011115610b1e57600080fd5b803590602001918460018302840111600160201b83111715610b3f57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610b9157600080fd5b820183602082011115610ba357600080fd5b803590602001918460018302840111600160201b83111715610bc457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610c1657600080fd5b820183602082011115610c2857600080fd5b803590602001918460018302840111600160201b83111715610c4957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b031690506125f1565b610285600480360360a0811015610cbc57600080fd5b810190602081018135600160201b811115610cd657600080fd5b820183602082011115610ce857600080fd5b803590602001918460018302840111600160201b83111715610d0957600080fd5b919390929091602081019035600160201b811115610d2657600080fd5b820183602082011115610d3857600080fd5b803590602001918460018302840111600160201b83111715610d5957600080fd5b91935091508035906020810135151590604001351515612691565b610285600480360360a0811015610d8a57600080fd5b810190602081018135600160201b811115610da457600080fd5b820183602082011115610db657600080fd5b803590602001918460018302840111600160201b83111715610dd757600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610e2957600080fd5b820183602082011115610e3b57600080fd5b803590602001918460018302840111600160201b83111715610e5c57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610eae57600080fd5b820183602082011115610ec057600080fd5b803590602001918460018302840111600160201b83111715610ee157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff83358116945060209093013590921691506127869050565b61028560048036036040811015610f4957600080fd5b810190602081018135600160201b811115610f6357600080fd5b820183602082011115610f7557600080fd5b803590602001918460018302840111600160201b83111715610f9657600080fd5b91935091503561292f565b6109b260048036036040811015610fb757600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610fe157600080fd5b820183602082011115610ff357600080fd5b803590602001918460018302840111600160201b8311171561101457600080fd5b5090925090506129bc565b6102856004803603606081101561103557600080fd5b810190602081018135600160201b81111561104f57600080fd5b82018360208201111561106157600080fd5b803590602001918460018302840111600160201b8311171561108257600080fd5b91935091506001600160a01b038135169060200135612a74565b610285600480360360808110156110b257600080fd5b810190602081018135600160201b8111156110cc57600080fd5b8201836020820111156110de57600080fd5b803590602001918460018302840111600160201b831117156110ff57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561115157600080fd5b82018360208201111561116357600080fd5b803590602001918460018302840111600160201b8311171561118457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff8335811694506020909301359092169150612b2c9050565b610285600480360360a08110156111ec57600080fd5b810190602081018135600160201b81111561120657600080fd5b82018360208201111561121857600080fd5b803590602001918460018302840111600160201b8311171561123957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561128b57600080fd5b82018360208201111561129d57600080fd5b803590602001918460018302840111600160201b831117156112be57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561131057600080fd5b82018360208201111561132257600080fd5b803590602001918460018302840111600160201b8311171561134357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff8335811694506020909301359092169150612c629050565b6109b2600480360360c08110156113ab57600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b8111156113f157600080fd5b82018360208201111561140357600080fd5b803590602001918460018302840111600160201b8311171561142457600080fd5b509092509050612cf7565b6109b26004803603604081101561144557600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561146f57600080fd5b82018360208201111561148157600080fd5b803590602001918460018302840111600160201b831117156114a257600080fd5b509092509050612dde565b610285600480360360408110156114c357600080fd5b5080359060200135612e62565b610285600480360360408110156114e657600080fd5b810190602081018135600160201b81111561150057600080fd5b82018360208201111561151257600080fd5b803590602001918460018302840111600160201b8311171561153357600080fd5b919390929091602081019035600160201b81111561155057600080fd5b82018360208201111561156257600080fd5b803590602001918460018302840111600160201b8311171561158357600080fd5b509092509050612ed0565b610285600480360360408110156115a457600080fd5b810190602081018135600160201b8111156115be57600080fd5b8201836020820111156115d057600080fd5b803590602001918460018302840111600160201b831117156115f157600080fd5b9193509150356001600160a01b0316612f84565b6102856004803603604081101561161b57600080fd5b810190602081018135600160201b81111561163557600080fd5b82018360208201111561164757600080fd5b803590602001918460018302840111600160201b8311171561166857600080fd5b919350915035613016565b6109b26004803603602081101561168957600080fd5b50356001600160a01b03166130a3565b610285600480360360a08110156116af57600080fd5b810190602081018135600160201b8111156116c957600080fd5b8201836020820111156116db57600080fd5b803590602001918460018302840111600160201b831117156116fc57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561174e57600080fd5b82018360208201111561176057600080fd5b803590602001918460018302840111600160201b8311171561178157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156117d357600080fd5b8201836020820111156117e557600080fd5b803590602001918460018302840111600160201b8311171561180657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff83358116945060209093013590921691506131269050565b6118c66004803603602081101561186e57600080fd5b810190602081018135600160201b81111561188857600080fd5b82018360208201111561189a57600080fd5b803590602001918460018302840111600160201b831117156118bb57600080fd5b5090925090506131bb565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b8381101561192557818101518382015260200161190d565b50505050905090810190601f1680156119525780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b8381101561198557818101518382015260200161196d565b50505050905090810190601f1680156119b25780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b610285600480360360c08110156119da57600080fd5b810190602081018135600160201b8111156119f457600080fd5b820183602082011115611a0657600080fd5b803590602001918460018302840111600160201b83111715611a2757600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611a7957600080fd5b820183602082011115611a8b57600080fd5b803590602001918460018302840111600160201b83111715611aac57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611afe57600080fd5b820183602082011115611b1057600080fd5b803590602001918460018302840111600160201b83111715611b3157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b03169050613325565b6000546001600160a01b031690565b600054604051600160e01b63888430410281526001600160a01b03838116602483015233604483018190526060600484019081526064840187905291909316926388843041928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b505af1158015611c43573d6000803e3d6000fd5b50505050505050565b600054604051600160e51b62db0811028152606060048201908152606482018890526001600160a01b0390921691631b610220918991899189918991899189918190602481019060448101906084018a8a80828437600083820152601f01601f191690910185810384528881526020019050888880828437600083820152601f01601f191690910185810383528681526020019050868680828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b158015611d2357600080fd5b505af1158015611d37573d6000803e3d6000fd5b50505050505050505050565b60008054604051600160e01b6368a6127302815261ffff8086166084830152841660a48201523360c4820181905260e0600483019081528a5160e48401528a516001600160a01b03909416946368a61273948c948c948c948c948c948c9483926024820192604483019260648101926101049091019160208f01918190849084905b83811015611ddd578181015183820152602001611dc5565b50505050905090810190601f168015611e0a5780820380516001836020036101000a031916815260200191505b5085810384528b5181528b516020918201918d019080838360005b83811015611e3d578181015183820152602001611e25565b50505050905090810190601f168015611e6a5780820380516001836020036101000a031916815260200191505b5085810383528a5181528a516020918201918c019080838360005b83811015611e9d578181015183820152602001611e85565b50505050905090810190601f168015611eca5780820380516001836020036101000a031916815260200191505b5085810382528951815289516020918201918b019080838360005b83811015611efd578181015183820152602001611ee5565b50505050905090810190601f168015611f2a5780820380516001836020036101000a031916815260200191505b509b505050505050505050505050600060405180830381600087803b158015611d2357600080fd5b600054604051600160e01b638baa81910281526001600160a01b03878116600483019081523360648401819052608060248501908152608485018990529290941693638baa8191938a938a938a938a938a9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b15801561201457600080fd5b505af1158015612028573d6000803e3d6000fd5b505050505050505050565b600054604051600160e01b634b20f45f0281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692634b20f45f928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b60008054604051600160e21b632e6dff9b02815261ffff80871660648301528516608482015260a481018490523360c4820181905260e0600483019081528a5160e48401528a516001600160a01b039094169463b9b7fe6c948c948c948c948c948c948c94839260248201926044830192610104019160208e0191908190849084905b83811015612160578181015183820152602001612148565b50505050905090810190601f16801561218d5780820380516001836020036101000a031916815260200191505b5084810383528a5181528a516020918201918c019080838360005b838110156121c05781810151838201526020016121a8565b50505050905090810190601f1680156121ed5780820380516001836020036101000a031916815260200191505b5084810382528951815289516020918201918b019080838360005b83811015612220578181015183820152602001612208565b50505050905090810190601f16801561224d5780820380516001836020036101000a031916815260200191505b509a5050505050505050505050600060405180830381600087803b158015611d2357600080fd5b600054604051600160e01b63404bf3eb0281526001600160a01b038581166024830152336064830181905260806004840190815260848401899052919093169263404bf3eb9289928992899289928992918190604481019060a401898980828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b15801561201457600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b03166344478e796040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561238457600080fd5b505af1158015612398573d6000803e3d6000fd5b505050506040513d60208110156123ae57600080fd5b5051905090565b60008054604051600160e01b6345a59e5b02815261ffff84166044820152606060048201908152606482018890526001600160a01b03909216916345a59e5b91899189918991899189919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505097505050505050505060206040518083038186803b15801561246b57600080fd5b505afa15801561247f573d6000803e3d6000fd5b505050506040513d602081101561249557600080fd5b50519695505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316634cbfa82e6040518163ffffffff1660e01b815260040160206040518083038186803b1580156124f057600080fd5b505afa158015612398573d6000803e3d6000fd5b6000805460408051600160e11b6327f2bf3d0281526001600160a01b03858116600483015291519190921692634fe57e7a926024808201939182900301818387803b15801561255257600080fd5b505af1158015612566573d6000803e3d6000fd5b5050505050565b6002546001600160a01b031633146125cf5760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b60008054604051600160e01b63e91b0e1902815261ffff8087166064830152851660848201526001600160a01b0384811660a48301523360c4830181905260e0600484019081528b5160e48501528b51929094169463e91b0e19948c948c948c948c948c948c9492939092839260248201926044830192610104019160208e01919081908490849083811015612160578181015183820152602001612148565b600054604051600160e11b630d82613b02815260448101859052831515606482015282151560848201523360a4820181905260c06004830190815260c483018a90526001600160a01b0390931692631b04c276928b928b928b928b928b928b928b9291908190602481019060e4018b8b80828437600083820152601f01601f191690910184810383528981526020019050898980828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b15801561276557600080fd5b505af1158015612779573d6000803e3d6000fd5b5050505050505050505050565b60008054604051600160e61b6302810afd02815261ffff8086166064830152841660848201523360a4820181905260c060048301908152895160c484015289516001600160a01b039094169463a042bf40948b948b948b948b948b949293919283926024810192604482019260e49092019160208d01918190849084905b8381101561281c578181015183820152602001612804565b50505050905090810190601f1680156128495780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b8381101561287c578181015183820152602001612864565b50505050905090810190601f1680156128a95780820380516001836020036101000a031916815260200191505b5084810382528851815288516020918201918a019080838360005b838110156128dc5781810151838201526020016128c4565b50505050905090810190601f1680156129095780820380516001836020036101000a031916815260200191505b509950505050505050505050600060405180830381600087803b15801561201457600080fd5b600054604051600160e21b632d551959028152602481018390523360448201819052606060048301908152606483018690526001600160a01b039093169263b5546564928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b6000805460408051600160e11b6335ab46bb0281526001600160a01b03878116600483019081526024830193845260448301879052931692636b568d76928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015612a4057600080fd5b505afa158015612a54573d6000803e3d6000fd5b505050506040513d6020811015612a6a57600080fd5b5051949350505050565b600054604051600160e11b6302740f8f0281526001600160a01b0384811660248301526044820184905233606483018190526080600484019081526084840188905291909316926304e81f1e92889288928892889290819060a401878780828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612b0e57600080fd5b505af1158015612b22573d6000803e3d6000fd5b5050505050505050565b60008054604051600160e11b634341e3ff02815261ffff8086166044830152841660648201526080600482019081528751608483015287516001600160a01b0390931693638683c7fe93899389938993899391928392602482019260a49092019160208a0191908190849084905b83811015612bb2578181015183820152602001612b9a565b50505050905090810190601f168015612bdf5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015612c12578181015183820152602001612bfa565b50505050905090810190601f168015612c3f5780820380516001836020036101000a031916815260200191505b509650505050505050600060405180830381600087803b158015612b0e57600080fd5b60008054604051600160e01b63d621d95702815261ffff8086166064830152841660848201523360a4820181905260c060048301908152895160c484015289516001600160a01b039094169463d621d957948b948b948b948b948b949293919283926024810192604482019260e49092019160208d01918190849084908381101561281c578181015183820152602001612804565b60008054604051600160e01b63936421d50281526001600160a01b038a8116600483019081528a82166024840152604483018a9052606483018990526084830188905260c060a4840190815260c48401879052919093169263936421d5928c928c928c928c928c928c928c929060e401848480828437600081840152601f19601f8201169050808301925050509850505050505050505060206040518083038186803b158015612da657600080fd5b505afa158015612dba573d6000803e3d6000fd5b505050506040513d6020811015612dd057600080fd5b505198975050505050505050565b6000805460408051600160e01b639bd381010281526001600160a01b03878116600483019081526024830193845260448301879052931692639bd38101928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015612a4057600080fd5b6000805460408051600160e31b6314b087e1028152600481018690526024810185905290516001600160a01b039092169263a5843f089260448084019382900301818387803b158015612eb457600080fd5b505af1158015612ec8573d6000803e3d6000fd5b505050505050565b600054604051600160e11b632e52d6df0281523360448201819052606060048301908152606483018790526001600160a01b0390931692635ca5adbe928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015612b0e57600080fd5b600054604051600160e11b630e124c890281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692631c249912928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b600054604051600160e01b633cf5f33b028152602481018390523360448201819052606060048301908152606483018690526001600160a01b0390931692633cf5f33b928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b6000805460408051600160e51b63068d50610281526001600160a01b0385811660048301529151919092169163d1aa0c20916024808301926020929190829003018186803b1580156130f457600080fd5b505afa158015613108573d6000803e3d6000fd5b505050506040513d602081101561311e57600080fd5b505192915050565b60008054604051600160e01b63ecad01d502815261ffff8086166064830152841660848201523360a4820181905260c060048301908152895160c484015289516001600160a01b039094169463ecad01d5948b948b948b948b948b949293919283926024810192604482019260e49092019160208d01918190849084908381101561281c578181015183820152602001612804565b60008054604051600160e01b63f346a3a7028152602060048201908152602482018590526060938493909283926001600160a01b039092169163f346a3a791899189918190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561323d57600080fd5b505afa158015613251573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052608081101561327a57600080fd5b810190808051600160201b81111561329157600080fd5b820160208101848111156132a457600080fd5b8151600160201b8111828201871017156132bd57600080fd5b50509291906020018051600160201b8111156132d857600080fd5b820160208101848111156132eb57600080fd5b8151600160201b81118282018710171561330457600080fd5b50506020820151604090920151949b909a5090985092965091945050505050565b60008054604051600160e11b637baf850302815261ffff8087166064830152851660848201526001600160a01b0384811660a48301523360c4830181905260e0600484019081528b5160e48501528b51929094169463f75f0a06948c948c948c948c948c948c9492939092839260248201926044830192610104019160208e0191908190849084908381101561216057818101518382015260200161214856fea165627a7a72305820b6e6a59155f89147a70225e82521ad7432c887b50d3e810e125f0b82322d043f0029" + +// DeployPermInterface deploys a new Ethereum contract, binding an instance of PermInterface to it. +func DeployPermInterface(auth *bind.TransactOpts, backend bind.ContractBackend, _permImplUpgradeable common.Address) (common.Address, *types.Transaction, *PermInterface, error) { + parsed, err := abi.JSON(strings.NewReader(PermInterfaceABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PermInterfaceBin), backend, _permImplUpgradeable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &PermInterface{PermInterfaceCaller: PermInterfaceCaller{contract: contract}, PermInterfaceTransactor: PermInterfaceTransactor{contract: contract}, PermInterfaceFilterer: PermInterfaceFilterer{contract: contract}}, nil +} + +// PermInterface is an auto generated Go binding around an Ethereum contract. +type PermInterface struct { + PermInterfaceCaller // Read-only binding to the contract + PermInterfaceTransactor // Write-only binding to the contract + PermInterfaceFilterer // Log filterer for contract events +} + +// PermInterfaceCaller is an auto generated read-only Go binding around an Ethereum contract. +type PermInterfaceCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermInterfaceTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PermInterfaceTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermInterfaceFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PermInterfaceFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermInterfaceSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PermInterfaceSession struct { + Contract *PermInterface // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermInterfaceCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PermInterfaceCallerSession struct { + Contract *PermInterfaceCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PermInterfaceTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PermInterfaceTransactorSession struct { + Contract *PermInterfaceTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermInterfaceRaw is an auto generated low-level Go binding around an Ethereum contract. +type PermInterfaceRaw struct { + Contract *PermInterface // Generic contract binding to access the raw methods on +} + +// PermInterfaceCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PermInterfaceCallerRaw struct { + Contract *PermInterfaceCaller // Generic read-only contract binding to access the raw methods on +} + +// PermInterfaceTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PermInterfaceTransactorRaw struct { + Contract *PermInterfaceTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPermInterface creates a new instance of PermInterface, bound to a specific deployed contract. +func NewPermInterface(address common.Address, backend bind.ContractBackend) (*PermInterface, error) { + contract, err := bindPermInterface(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PermInterface{PermInterfaceCaller: PermInterfaceCaller{contract: contract}, PermInterfaceTransactor: PermInterfaceTransactor{contract: contract}, PermInterfaceFilterer: PermInterfaceFilterer{contract: contract}}, nil +} + +// NewPermInterfaceCaller creates a new read-only instance of PermInterface, bound to a specific deployed contract. +func NewPermInterfaceCaller(address common.Address, caller bind.ContractCaller) (*PermInterfaceCaller, error) { + contract, err := bindPermInterface(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PermInterfaceCaller{contract: contract}, nil +} + +// NewPermInterfaceTransactor creates a new write-only instance of PermInterface, bound to a specific deployed contract. +func NewPermInterfaceTransactor(address common.Address, transactor bind.ContractTransactor) (*PermInterfaceTransactor, error) { + contract, err := bindPermInterface(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PermInterfaceTransactor{contract: contract}, nil +} + +// NewPermInterfaceFilterer creates a new log filterer instance of PermInterface, bound to a specific deployed contract. +func NewPermInterfaceFilterer(address common.Address, filterer bind.ContractFilterer) (*PermInterfaceFilterer, error) { + contract, err := bindPermInterface(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PermInterfaceFilterer{contract: contract}, nil +} + +// bindPermInterface binds a generic wrapper to an already deployed contract. +func bindPermInterface(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PermInterfaceABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermInterface *PermInterfaceRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermInterface.Contract.PermInterfaceCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermInterface *PermInterfaceRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermInterface.Contract.PermInterfaceTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermInterface *PermInterfaceRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermInterface.Contract.PermInterfaceTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermInterface *PermInterfaceCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermInterface.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermInterface *PermInterfaceTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermInterface.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermInterface *PermInterfaceTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermInterface.Contract.contract.Transact(opts, method, params...) +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) ConnectionAllowed(opts *bind.CallOpts, _enodeId string, _ip string, _port uint16) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "connectionAllowed", _enodeId, _ip, _port) + return *ret0, err +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_PermInterface *PermInterfaceSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { + return _PermInterface.Contract.ConnectionAllowed(&_PermInterface.CallOpts, _enodeId, _ip, _port) +} + +// ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. +// +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { + return _PermInterface.Contract.ConnectionAllowed(&_PermInterface.CallOpts, _enodeId, _ip, _port) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermInterface *PermInterfaceCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "getNetworkBootStatus") + return *ret0, err +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermInterface *PermInterfaceSession) GetNetworkBootStatus() (bool, error) { + return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) +} + +// GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. +// +// Solidity: function getNetworkBootStatus() constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) GetNetworkBootStatus() (bool, error) { + return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermInterface *PermInterfaceCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(common.Address) + ret3 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _PermInterface.contract.Call(opts, out, "getPendingOp", _orgId) + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermInterface *PermInterfaceSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) +} + +// GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. +// +// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +func (_PermInterface *PermInterfaceCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { + return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) +} + +// GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. +// +// Solidity: function getPermissionsImpl() constant returns(address) +func (_PermInterface *PermInterfaceCaller) GetPermissionsImpl(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "getPermissionsImpl") + return *ret0, err +} + +// GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. +// +// Solidity: function getPermissionsImpl() constant returns(address) +func (_PermInterface *PermInterfaceSession) GetPermissionsImpl() (common.Address, error) { + return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) +} + +// GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. +// +// Solidity: function getPermissionsImpl() constant returns(address) +func (_PermInterface *PermInterfaceCallerSession) GetPermissionsImpl() (common.Address, error) { + return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "isNetworkAdmin", _account) + return *ret0, err +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermInterface *PermInterfaceSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) +} + +// IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. +// +// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { + return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) + return *ret0, err +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) +} + +// IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. +// +// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. +// +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) TransactionAllowed(opts *bind.CallOpts, _sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "transactionAllowed", _sender, _target, _value, _gasPrice, _gasLimit, _payload) + return *ret0, err +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. +// +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +func (_PermInterface *PermInterfaceSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { + return _PermInterface.Contract.TransactionAllowed(&_PermInterface.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. +// +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { + return _PermInterface.Contract.TransactionAllowed(&_PermInterface.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _PermInterface.contract.Call(opts, out, "validateAccount", _account, _orgId) + return *ret0, err +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) +} + +// ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. +// +// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +func (_PermInterface *PermInterfaceCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { + return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _acct) returns() +func (_PermInterface *PermInterfaceTransactor) AddAdminAccount(opts *bind.TransactOpts, _acct common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addAdminAccount", _acct) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _acct) returns() +func (_PermInterface *PermInterfaceSession) AddAdminAccount(_acct common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminAccount(&_PermInterface.TransactOpts, _acct) +} + +// AddAdminAccount is a paid mutator transaction binding the contract method 0x4fe57e7a. +// +// Solidity: function addAdminAccount(address _acct) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddAdminAccount(_acct common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminAccount(&_PermInterface.TransactOpts, _acct) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x8683c7fe. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactor) AddAdminNode(opts *bind.TransactOpts, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addAdminNode", _enodeId, _ip, _port, _raftport) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x8683c7fe. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceSession) AddAdminNode(_enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminNode(&_PermInterface.TransactOpts, _enodeId, _ip, _port, _raftport) +} + +// AddAdminNode is a paid mutator transaction binding the contract method 0x8683c7fe. +// +// Solidity: function addAdminNode(string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddAdminNode(_enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.AddAdminNode(&_PermInterface.TransactOpts, _enodeId, _ip, _port, _raftport) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x51f604c3. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin) returns() +func (_PermInterface *PermInterfaceTransactor) AddNewRole(opts *bind.TransactOpts, _roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addNewRole", _roleId, _orgId, _access, _voter, _admin) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x51f604c3. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin) returns() +func (_PermInterface *PermInterfaceSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool) (*types.Transaction, error) { + return _PermInterface.Contract.AddNewRole(&_PermInterface.TransactOpts, _roleId, _orgId, _access, _voter, _admin) +} + +// AddNewRole is a paid mutator transaction binding the contract method 0x51f604c3. +// +// Solidity: function addNewRole(string _roleId, string _orgId, uint256 _access, bool _voter, bool _admin) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddNewRole(_roleId string, _orgId string, _access *big.Int, _voter bool, _admin bool) (*types.Transaction, error) { + return _PermInterface.Contract.AddNewRole(&_PermInterface.TransactOpts, _roleId, _orgId, _access, _voter, _admin) +} + +// AddNode is a paid mutator transaction binding the contract method 0xef5f7196. +// +// Solidity: function addNode(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactor) AddNode(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addNode", _orgId, _enodeId, _ip, _port, _raftport) +} + +// AddNode is a paid mutator transaction binding the contract method 0xef5f7196. +// +// Solidity: function addNode(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceSession) AddNode(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.AddNode(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport) +} + +// AddNode is a paid mutator transaction binding the contract method 0xef5f7196. +// +// Solidity: function addNode(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddNode(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.AddNode(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport) +} + +// AddOrg is a paid mutator transaction binding the contract method 0x513a3277. +// +// Solidity: function addOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) AddOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addOrg", _orgId, _enodeId, _ip, _port, _raftport, _account) +} + +// AddOrg is a paid mutator transaction binding the contract method 0x513a3277. +// +// Solidity: function addOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account) returns() +func (_PermInterface *PermInterfaceSession) AddOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account) +} + +// AddOrg is a paid mutator transaction binding the contract method 0x513a3277. +// +// Solidity: function addOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.AddOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x2e125a6c. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactor) AddSubOrg(opts *bind.TransactOpts, _pOrgId string, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "addSubOrg", _pOrgId, _orgId, _enodeId, _ip, _port, _raftport) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x2e125a6c. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.AddSubOrg(&_PermInterface.TransactOpts, _pOrgId, _orgId, _enodeId, _ip, _port, _raftport) +} + +// AddSubOrg is a paid mutator transaction binding the contract method 0x2e125a6c. +// +// Solidity: function addSubOrg(string _pOrgId, string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactorSession) AddSubOrg(_pOrgId string, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.AddSubOrg(&_PermInterface.TransactOpts, _pOrgId, _orgId, _enodeId, _ip, _port, _raftport) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x16724c44. +// +// Solidity: function approveAdminRole(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveAdminRole", _orgId, _account) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x16724c44. +// +// Solidity: function approveAdminRole(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceSession) ApproveAdminRole(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveAdminRole(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveAdminRole is a paid mutator transaction binding the contract method 0x16724c44. +// +// Solidity: function approveAdminRole(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveAdminRole(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveAdminRole(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x3e239b23. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveBlacklistedAccountRecovery", _orgId, _account) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x3e239b23. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0x3e239b23. +// +// Solidity: function approveBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x58dcff71. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveBlacklistedNodeRecovery", _orgId, _enodeId, _ip, _port, _raftport) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x58dcff71. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport) +} + +// ApproveBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x58dcff71. +// +// Solidity: function approveBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xfa279d61. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveOrg(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveOrg", _orgId, _enodeId, _ip, _port, _raftport, _account) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xfa279d61. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account) returns() +func (_PermInterface *PermInterfaceSession) ApproveOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account) +} + +// ApproveOrg is a paid mutator transaction binding the contract method 0xfa279d61. +// +// Solidity: function approveOrg(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveOrg(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrg(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _account) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0x5be9672c. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) ApproveOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "approveOrgStatus", _orgId, _action) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0x5be9672c. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) ApproveOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} + +// ApproveOrgStatus is a paid mutator transaction binding the contract method 0x5be9672c. +// +// Solidity: function approveOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) ApproveOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.ApproveOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x2f7f0a12. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactor) AssignAccountRole(opts *bind.TransactOpts, _account common.Address, _orgId string, _roleId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "assignAccountRole", _account, _orgId, _roleId) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x2f7f0a12. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId) returns() +func (_PermInterface *PermInterfaceSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAccountRole(&_PermInterface.TransactOpts, _account, _orgId, _roleId) +} + +// AssignAccountRole is a paid mutator transaction binding the contract method 0x2f7f0a12. +// +// Solidity: function assignAccountRole(address _account, string _orgId, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AssignAccountRole(_account common.Address, _orgId string, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAccountRole(&_PermInterface.TransactOpts, _account, _orgId, _roleId) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x43de646c. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactor) AssignAdminRole(opts *bind.TransactOpts, _orgId string, _account common.Address, _roleId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "assignAdminRole", _orgId, _account, _roleId) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x43de646c. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId) returns() +func (_PermInterface *PermInterfaceSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAdminRole(&_PermInterface.TransactOpts, _orgId, _account, _roleId) +} + +// AssignAdminRole is a paid mutator transaction binding the contract method 0x43de646c. +// +// Solidity: function assignAdminRole(string _orgId, address _account, string _roleId) returns() +func (_PermInterface *PermInterfaceTransactorSession) AssignAdminRole(_orgId string, _account common.Address, _roleId string) (*types.Transaction, error) { + return _PermInterface.Contract.AssignAdminRole(&_PermInterface.TransactOpts, _orgId, _account, _roleId) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermInterface *PermInterfaceTransactor) Init(opts *bind.TransactOpts, _breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "init", _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermInterface *PermInterfaceSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.Init(&_PermInterface.TransactOpts, _breadth, _depth) +} + +// Init is a paid mutator transaction binding the contract method 0xa5843f08. +// +// Solidity: function init(uint256 _breadth, uint256 _depth) returns() +func (_PermInterface *PermInterfaceTransactorSession) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.Init(&_PermInterface.TransactOpts, _breadth, _depth) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_PermInterface *PermInterfaceTransactor) RemoveRole(opts *bind.TransactOpts, _roleId string, _orgId string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "removeRole", _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_PermInterface *PermInterfaceSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _PermInterface.Contract.RemoveRole(&_PermInterface.TransactOpts, _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_PermInterface *PermInterfaceTransactorSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _PermInterface.Contract.RemoveRole(&_PermInterface.TransactOpts, _roleId, _orgId) +} + +// SetPermImplementation is a paid mutator transaction binding the contract method 0x511bbd9f. +// +// Solidity: function setPermImplementation(address _permImplementation) returns() +func (_PermInterface *PermInterfaceTransactor) SetPermImplementation(opts *bind.TransactOpts, _permImplementation common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "setPermImplementation", _permImplementation) +} + +// SetPermImplementation is a paid mutator transaction binding the contract method 0x511bbd9f. +// +// Solidity: function setPermImplementation(address _permImplementation) returns() +func (_PermInterface *PermInterfaceSession) SetPermImplementation(_permImplementation common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.SetPermImplementation(&_PermInterface.TransactOpts, _permImplementation) +} + +// SetPermImplementation is a paid mutator transaction binding the contract method 0x511bbd9f. +// +// Solidity: function setPermImplementation(address _permImplementation) returns() +func (_PermInterface *PermInterfaceTransactorSession) SetPermImplementation(_permImplementation common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.SetPermImplementation(&_PermInterface.TransactOpts, _permImplementation) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermInterface *PermInterfaceTransactor) SetPolicy(opts *bind.TransactOpts, _nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "setPolicy", _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermInterface *PermInterfaceSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermInterface.Contract.SetPolicy(&_PermInterface.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// SetPolicy is a paid mutator transaction binding the contract method 0x1b610220. +// +// Solidity: function setPolicy(string _nwAdminOrg, string _nwAdminRole, string _oAdminRole) returns() +func (_PermInterface *PermInterfaceTransactorSession) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return _PermInterface.Contract.SetPolicy(&_PermInterface.TransactOpts, _nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0xa97914bf. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactor) StartBlacklistedAccountRecovery(opts *bind.TransactOpts, _orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "startBlacklistedAccountRecovery", _orgId, _account) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0xa97914bf. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// StartBlacklistedAccountRecovery is a paid mutator transaction binding the contract method 0xa97914bf. +// +// Solidity: function startBlacklistedAccountRecovery(string _orgId, address _account) returns() +func (_PermInterface *PermInterfaceTransactorSession) StartBlacklistedAccountRecovery(_orgId string, _account common.Address) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedAccountRecovery(&_PermInterface.TransactOpts, _orgId, _account) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x91ba3f96. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactor) StartBlacklistedNodeRecovery(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "startBlacklistedNodeRecovery", _orgId, _enodeId, _ip, _port, _raftport) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x91ba3f96. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport) +} + +// StartBlacklistedNodeRecovery is a paid mutator transaction binding the contract method 0x91ba3f96. +// +// Solidity: function startBlacklistedNodeRecovery(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport) returns() +func (_PermInterface *PermInterfaceTransactorSession) StartBlacklistedNodeRecovery(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16) (*types.Transaction, error) { + return _PermInterface.Contract.StartBlacklistedNodeRecovery(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) UpdateAccountStatus(opts *bind.TransactOpts, _orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateAccountStatus", _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateAccountStatus(&_PermInterface.TransactOpts, _orgId, _account, _action) +} + +// UpdateAccountStatus is a paid mutator transaction binding the contract method 0x84b7a84a. +// +// Solidity: function updateAccountStatus(string _orgId, address _account, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) UpdateAccountStatus(_orgId string, _account common.Address, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateAccountStatus(&_PermInterface.TransactOpts, _orgId, _account, _action) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermInterface *PermInterfaceTransactor) UpdateNetworkBootStatus(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateNetworkBootStatus") +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermInterface *PermInterfaceSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNetworkBootStatus(&_PermInterface.TransactOpts) +} + +// UpdateNetworkBootStatus is a paid mutator transaction binding the contract method 0x44478e79. +// +// Solidity: function updateNetworkBootStatus() returns(bool) +func (_PermInterface *PermInterfaceTransactorSession) UpdateNetworkBootStatus() (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNetworkBootStatus(&_PermInterface.TransactOpts) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x3f9be497. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) UpdateNodeStatus(opts *bind.TransactOpts, _orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateNodeStatus", _orgId, _enodeId, _ip, _port, _raftport, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x3f9be497. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) UpdateNodeStatus(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNodeStatus(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _action) +} + +// UpdateNodeStatus is a paid mutator transaction binding the contract method 0x3f9be497. +// +// Solidity: function updateNodeStatus(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) UpdateNodeStatus(_orgId string, _enodeId string, _ip string, _port uint16, _raftport uint16, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateNodeStatus(&_PermInterface.TransactOpts, _orgId, _enodeId, _ip, _port, _raftport, _action) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0xbb3b6e80. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactor) UpdateOrgStatus(opts *bind.TransactOpts, _orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.contract.Transact(opts, "updateOrgStatus", _orgId, _action) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0xbb3b6e80. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceSession) UpdateOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} + +// UpdateOrgStatus is a paid mutator transaction binding the contract method 0xbb3b6e80. +// +// Solidity: function updateOrgStatus(string _orgId, uint256 _action) returns() +func (_PermInterface *PermInterfaceTransactorSession) UpdateOrgStatus(_orgId string, _action *big.Int) (*types.Transaction, error) { + return _PermInterface.Contract.UpdateOrgStatus(&_PermInterface.TransactOpts, _orgId, _action) +} diff --git a/permission/v2/bind/permission_upgr.go b/permission/v2/bind/permission_upgr.go new file mode 100644 index 0000000000..133e54ed7e --- /dev/null +++ b/permission/v2/bind/permission_upgr.go @@ -0,0 +1,313 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PermUpgrABI is the input ABI used to generate the binding from. +const PermUpgrABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"getPermImpl\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_proposedImpl\",\"type\":\"address\"}],\"name\":\"confirmImplChange\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getGuardian\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getPermInterface\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_permInterface\",\"type\":\"address\"},{\"name\":\"_permImpl\",\"type\":\"address\"}],\"name\":\"init\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_guardian\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" + +var PermUpgrParsedABI, _ = abi.JSON(strings.NewReader(PermUpgrABI)) + +// PermUpgrBin is the compiled bytecode used for deploying new contracts. +var PermUpgrBin = "0x608060405234801561001057600080fd5b506040516020806106e78339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905560028054600160a01b60ff0219169055610675806100726000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e32cf901461005c57806322bcb39a14610080578063a75b87d2146100a8578063e572515c146100b0578063f09a4016146100b8575b600080fd5b6100646100e6565b604080516001600160a01b039092168252519081900360200190f35b6100a66004803603602081101561009657600080fd5b50356001600160a01b03166100f5565b005b61006461030b565b61006461031a565b6100a6600480360360408110156100ce57600080fd5b506001600160a01b0381358116916020013516610329565b6001546001600160a01b031690565b6000546001600160a01b0316331461014b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60608060606000600160009054906101000a90046001600160a01b03166001600160a01b031663cc9ba6fa6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a057600080fd5b505afa1580156101b4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156101dd57600080fd5b8101908080516401000000008111156101f557600080fd5b8201602081018481111561020857600080fd5b815164010000000081118282018710171561022257600080fd5b5050929190602001805164010000000081111561023e57600080fd5b8201602081018481111561025157600080fd5b815164010000000081118282018710171561026b57600080fd5b5050929190602001805164010000000081111561028757600080fd5b8201602081018481111561029a57600080fd5b81516401000000008111828201871017156102b457600080fd5b50506020909101519498509296509194509192506102d9915086905085858585610443565b600180546001600160a01b0319166001600160a01b03878116919091179182905561030491166105e4565b5050505050565b6000546001600160a01b031690565b6002546001600160a01b031690565b6000546001600160a01b0316331461037f5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600254600160a01b900460ff16156103e15760408051600160e51b62461bcd02815260206004820152601960248201527f63616e206265206578656375746564206f6e6c79206f6e636500000000000000604482015290519081900360640190fd5b600180546001600160a01b038084166001600160a01b031992831617928390556002805486831693169290921790915561041b91166105e4565b50506002805474ff00000000000000000000000000000000000000001916600160a01b179055565b846001600160a01b031663f5ad584a858585856040518563ffffffff1660e01b81526004018080602001806020018060200185151515158152602001848103845288818151815260200191508051906020019080838360005b838110156104b457818101518382015260200161049c565b50505050905090810190601f1680156104e15780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156105145781810151838201526020016104fc565b50505050905090810190601f1680156105415780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b8381101561057457818101518382015260200161055c565b50505050905090810190601f1680156105a15780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156105c557600080fd5b505af11580156105d9573d6000803e3d6000fd5b505050505050505050565b60025460408051600160e01b63511bbd9f0281526001600160a01b0384811660048301529151919092169163511bbd9f91602480830192600092919082900301818387803b15801561063557600080fd5b505af1158015610304573d6000803e3d6000fdfea165627a7a7230582055489d1e43ffd1f6646b629ccf78d3fb7551dd246e111ec3ccbf9ae12f8b900a0029" + +// DeployPermUpgr deploys a new Ethereum contract, binding an instance of PermUpgr to it. +func DeployPermUpgr(auth *bind.TransactOpts, backend bind.ContractBackend, _guardian common.Address) (common.Address, *types.Transaction, *PermUpgr, error) { + parsed, err := abi.JSON(strings.NewReader(PermUpgrABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PermUpgrBin), backend, _guardian) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &PermUpgr{PermUpgrCaller: PermUpgrCaller{contract: contract}, PermUpgrTransactor: PermUpgrTransactor{contract: contract}, PermUpgrFilterer: PermUpgrFilterer{contract: contract}}, nil +} + +// PermUpgr is an auto generated Go binding around an Ethereum contract. +type PermUpgr struct { + PermUpgrCaller // Read-only binding to the contract + PermUpgrTransactor // Write-only binding to the contract + PermUpgrFilterer // Log filterer for contract events +} + +// PermUpgrCaller is an auto generated read-only Go binding around an Ethereum contract. +type PermUpgrCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermUpgrTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PermUpgrTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermUpgrFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PermUpgrFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PermUpgrSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PermUpgrSession struct { + Contract *PermUpgr // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermUpgrCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PermUpgrCallerSession struct { + Contract *PermUpgrCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PermUpgrTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PermUpgrTransactorSession struct { + Contract *PermUpgrTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PermUpgrRaw is an auto generated low-level Go binding around an Ethereum contract. +type PermUpgrRaw struct { + Contract *PermUpgr // Generic contract binding to access the raw methods on +} + +// PermUpgrCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PermUpgrCallerRaw struct { + Contract *PermUpgrCaller // Generic read-only contract binding to access the raw methods on +} + +// PermUpgrTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PermUpgrTransactorRaw struct { + Contract *PermUpgrTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPermUpgr creates a new instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgr(address common.Address, backend bind.ContractBackend) (*PermUpgr, error) { + contract, err := bindPermUpgr(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PermUpgr{PermUpgrCaller: PermUpgrCaller{contract: contract}, PermUpgrTransactor: PermUpgrTransactor{contract: contract}, PermUpgrFilterer: PermUpgrFilterer{contract: contract}}, nil +} + +// NewPermUpgrCaller creates a new read-only instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgrCaller(address common.Address, caller bind.ContractCaller) (*PermUpgrCaller, error) { + contract, err := bindPermUpgr(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PermUpgrCaller{contract: contract}, nil +} + +// NewPermUpgrTransactor creates a new write-only instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgrTransactor(address common.Address, transactor bind.ContractTransactor) (*PermUpgrTransactor, error) { + contract, err := bindPermUpgr(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PermUpgrTransactor{contract: contract}, nil +} + +// NewPermUpgrFilterer creates a new log filterer instance of PermUpgr, bound to a specific deployed contract. +func NewPermUpgrFilterer(address common.Address, filterer bind.ContractFilterer) (*PermUpgrFilterer, error) { + contract, err := bindPermUpgr(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PermUpgrFilterer{contract: contract}, nil +} + +// bindPermUpgr binds a generic wrapper to an already deployed contract. +func bindPermUpgr(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PermUpgrABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermUpgr *PermUpgrRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermUpgr.Contract.PermUpgrCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermUpgr *PermUpgrRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermUpgr.Contract.PermUpgrTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermUpgr *PermUpgrRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermUpgr.Contract.PermUpgrTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PermUpgr *PermUpgrCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PermUpgr.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PermUpgr *PermUpgrTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PermUpgr.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PermUpgr *PermUpgrTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PermUpgr.Contract.contract.Transact(opts, method, params...) +} + +// GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. +// +// Solidity: function getGuardian() constant returns(address) +func (_PermUpgr *PermUpgrCaller) GetGuardian(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermUpgr.contract.Call(opts, out, "getGuardian") + return *ret0, err +} + +// GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. +// +// Solidity: function getGuardian() constant returns(address) +func (_PermUpgr *PermUpgrSession) GetGuardian() (common.Address, error) { + return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) +} + +// GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. +// +// Solidity: function getGuardian() constant returns(address) +func (_PermUpgr *PermUpgrCallerSession) GetGuardian() (common.Address, error) { + return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) +} + +// GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. +// +// Solidity: function getPermImpl() constant returns(address) +func (_PermUpgr *PermUpgrCaller) GetPermImpl(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermUpgr.contract.Call(opts, out, "getPermImpl") + return *ret0, err +} + +// GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. +// +// Solidity: function getPermImpl() constant returns(address) +func (_PermUpgr *PermUpgrSession) GetPermImpl() (common.Address, error) { + return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) +} + +// GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. +// +// Solidity: function getPermImpl() constant returns(address) +func (_PermUpgr *PermUpgrCallerSession) GetPermImpl() (common.Address, error) { + return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) +} + +// GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. +// +// Solidity: function getPermInterface() constant returns(address) +func (_PermUpgr *PermUpgrCaller) GetPermInterface(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _PermUpgr.contract.Call(opts, out, "getPermInterface") + return *ret0, err +} + +// GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. +// +// Solidity: function getPermInterface() constant returns(address) +func (_PermUpgr *PermUpgrSession) GetPermInterface() (common.Address, error) { + return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) +} + +// GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. +// +// Solidity: function getPermInterface() constant returns(address) +func (_PermUpgr *PermUpgrCallerSession) GetPermInterface() (common.Address, error) { + return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) +} + +// ConfirmImplChange is a paid mutator transaction binding the contract method 0x22bcb39a. +// +// Solidity: function confirmImplChange(address _proposedImpl) returns() +func (_PermUpgr *PermUpgrTransactor) ConfirmImplChange(opts *bind.TransactOpts, _proposedImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.contract.Transact(opts, "confirmImplChange", _proposedImpl) +} + +// ConfirmImplChange is a paid mutator transaction binding the contract method 0x22bcb39a. +// +// Solidity: function confirmImplChange(address _proposedImpl) returns() +func (_PermUpgr *PermUpgrSession) ConfirmImplChange(_proposedImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.ConfirmImplChange(&_PermUpgr.TransactOpts, _proposedImpl) +} + +// ConfirmImplChange is a paid mutator transaction binding the contract method 0x22bcb39a. +// +// Solidity: function confirmImplChange(address _proposedImpl) returns() +func (_PermUpgr *PermUpgrTransactorSession) ConfirmImplChange(_proposedImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.ConfirmImplChange(&_PermUpgr.TransactOpts, _proposedImpl) +} + +// Init is a paid mutator transaction binding the contract method 0xf09a4016. +// +// Solidity: function init(address _permInterface, address _permImpl) returns() +func (_PermUpgr *PermUpgrTransactor) Init(opts *bind.TransactOpts, _permInterface common.Address, _permImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.contract.Transact(opts, "init", _permInterface, _permImpl) +} + +// Init is a paid mutator transaction binding the contract method 0xf09a4016. +// +// Solidity: function init(address _permInterface, address _permImpl) returns() +func (_PermUpgr *PermUpgrSession) Init(_permInterface common.Address, _permImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.Init(&_PermUpgr.TransactOpts, _permInterface, _permImpl) +} + +// Init is a paid mutator transaction binding the contract method 0xf09a4016. +// +// Solidity: function init(address _permInterface, address _permImpl) returns() +func (_PermUpgr *PermUpgrTransactorSession) Init(_permInterface common.Address, _permImpl common.Address) (*types.Transaction, error) { + return _PermUpgr.Contract.Init(&_PermUpgr.TransactOpts, _permInterface, _permImpl) +} diff --git a/permission/v2/bind/roles.go b/permission/v2/bind/roles.go new file mode 100644 index 0000000000..f0d6a37451 --- /dev/null +++ b/permission/v2/bind/roles.go @@ -0,0 +1,770 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// RoleManagerABI is the input ABI used to generate the binding from. +const RoleManagerABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getRoleDetails\",\"outputs\":[{\"name\":\"roleId\",\"type\":\"string\"},{\"name\":\"orgId\",\"type\":\"string\"},{\"name\":\"accessType\",\"type\":\"uint256\"},{\"name\":\"voter\",\"type\":\"bool\"},{\"name\":\"admin\",\"type\":\"bool\"},{\"name\":\"active\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_baseAccess\",\"type\":\"uint256\"},{\"name\":\"_isVoter\",\"type\":\"bool\"},{\"name\":\"_isAdmin\",\"type\":\"bool\"}],\"name\":\"addRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getNumberOfRoles\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_rIndex\",\"type\":\"uint256\"}],\"name\":\"getRoleDetailsFromIndex\",\"outputs\":[{\"name\":\"roleId\",\"type\":\"string\"},{\"name\":\"orgId\",\"type\":\"string\"},{\"name\":\"accessType\",\"type\":\"uint256\"},{\"name\":\"voter\",\"type\":\"bool\"},{\"name\":\"admin\",\"type\":\"bool\"},{\"name\":\"active\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"removeRole\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"roleExists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"isAdminRole\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"roleAccess\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"},{\"name\":\"_typeOfTxn\",\"type\":\"uint256\"}],\"name\":\"transactionAllowed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_roleId\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_ultParent\",\"type\":\"string\"}],\"name\":\"isVoterRole\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_baseAccess\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_isVoter\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_isAdmin\",\"type\":\"bool\"}],\"name\":\"RoleCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_roleId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"}]" + +var RoleManagerParsedABI, _ = abi.JSON(strings.NewReader(RoleManagerABI)) + +// RoleManagerBin is the compiled bytecode used for deploying new contracts. +var RoleManagerBin = "0x608060405234801561001057600080fd5b506040516020806128d98339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055612877806100626000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c8063abf5739f11610066578063abf5739f1461048e578063be322e5414610650578063cfc83dfa1461075e578063d1f778661461090c578063deb16ba714610a1a5761009e565b80631870aba3146100a35780637b7135791461025f57806387f55d3114610399578063a451d4a8146103b3578063a6343012146103d0575b600080fd5b610161600480360360408110156100b957600080fd5b810190602081018135600160201b8111156100d357600080fd5b8201836020820111156100e557600080fd5b803590602001918460018302840111600160201b8311171561010657600080fd5b919390929091602081019035600160201b81111561012357600080fd5b82018360208201111561013557600080fd5b803590602001918460018302840111600160201b8311171561015657600080fd5b509092509050610b28565b604080519081018590528315156060820152821515608082015281151560a082015260c08082528751908201528651819060208083019160e08401918b019080838360005b838110156101be5781810151838201526020016101a6565b50505050905090810190601f1680156101eb5780820380516001836020036101000a031916815260200191505b5083810382528851815288516020918201918a019080838360005b8381101561021e578181015183820152602001610206565b50505050905090810190601f16801561024b5780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610397600480360360a081101561027557600080fd5b810190602081018135600160201b81111561028f57600080fd5b8201836020820111156102a157600080fd5b803590602001918460018302840111600160201b831117156102c257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561031457600080fd5b82018360208201111561032657600080fd5b803590602001918460018302840111600160201b8311171561034757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550508235935050506020810135151590604001351515610eae565b005b6103a1611454565b60408051918252519081900360200190f35b610161600480360360208110156103c957600080fd5b503561145b565b610397600480360360408110156103e657600080fd5b810190602081018135600160201b81111561040057600080fd5b82018360208201111561041257600080fd5b803590602001918460018302840111600160201b8311171561043357600080fd5b919390929091602081019035600160201b81111561045057600080fd5b82018360208201111561046257600080fd5b803590602001918460018302840111600160201b8311171561048357600080fd5b509092509050611679565b61063c600480360360608110156104a457600080fd5b810190602081018135600160201b8111156104be57600080fd5b8201836020820111156104d057600080fd5b803590602001918460018302840111600160201b831117156104f157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561054357600080fd5b82018360208201111561055557600080fd5b803590602001918460018302840111600160201b8311171561057657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156105c857600080fd5b8201836020820111156105da57600080fd5b803590602001918460018302840111600160201b831117156105fb57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611974945050505050565b604080519115158252519081900360200190f35b61063c6004803603606081101561066657600080fd5b810190602081018135600160201b81111561068057600080fd5b82018360208201111561069257600080fd5b803590602001918460018302840111600160201b831117156106b357600080fd5b919390929091602081019035600160201b8111156106d057600080fd5b8201836020820111156106e257600080fd5b803590602001918460018302840111600160201b8311171561070357600080fd5b919390929091602081019035600160201b81111561072057600080fd5b82018360208201111561073257600080fd5b803590602001918460018302840111600160201b8311171561075357600080fd5b509092509050611be8565b6103a16004803603606081101561077457600080fd5b810190602081018135600160201b81111561078e57600080fd5b8201836020820111156107a057600080fd5b803590602001918460018302840111600160201b831117156107c157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561081357600080fd5b82018360208201111561082557600080fd5b803590602001918460018302840111600160201b8311171561084657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561089857600080fd5b8201836020820111156108aa57600080fd5b803590602001918460018302840111600160201b831117156108cb57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611f68945050505050565b61063c6004803603608081101561092257600080fd5b810190602081018135600160201b81111561093c57600080fd5b82018360208201111561094e57600080fd5b803590602001918460018302840111600160201b8311171561096f57600080fd5b919390929091602081019035600160201b81111561098c57600080fd5b82018360208201111561099e57600080fd5b803590602001918460018302840111600160201b831117156109bf57600080fd5b919390929091602081019035600160201b8111156109dc57600080fd5b8201836020820111156109ee57600080fd5b803590602001918460018302840111600160201b83111715610a0f57600080fd5b9193509150356121c2565b61063c60048036036060811015610a3057600080fd5b810190602081018135600160201b811115610a4a57600080fd5b820183602082011115610a5c57600080fd5b803590602001918460018302840111600160201b83111715610a7d57600080fd5b919390929091602081019035600160201b811115610a9a57600080fd5b820183602082011115610aac57600080fd5b803590602001918460018302840111600160201b83111715610acd57600080fd5b919390929091602081019035600160201b811115610aea57600080fd5b820183602082011115610afc57600080fd5b803590602001918460018302840111600160201b83111715610b1d57600080fd5b50909250905061232e565b606080600080600080610bb28a8a8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8e018190048102820181019092528c815292508c91508b9081908401838280828437600092018290525060408051602081019091529081529250611974915050565b1515610c1c57898960008060008085858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506040805160208101909152908152939f50929d50959b509399509197509550610ea1945050505050565b6000610c918b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8f018190048102820181019092528d815292508d91508c90819084018382808284376000920191909152506126a392505050565b9050600181815481101515610ca257fe5b9060005260206000209060040201600001600182815481101515610cc257fe5b9060005260206000209060040201600101600183815481101515610ce257fe5b906000526020600020906004020160020154600184815481101515610d0357fe5b60009182526020909120600360049092020101546001805460ff9092169186908110610d2b57fe5b906000526020600020906004020160030160019054906101000a900460ff16600186815481101515610d5957fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff169290918891830182828015610dfe5780601f10610dd357610100808354040283529160200191610dfe565b820191906000526020600020905b815481529060010190602001808311610de157829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a945092508401905082828015610e8c5780601f10610e6157610100808354040283529160200191610e8c565b820191906000526020600020905b815481529060010190602001808311610e6f57829003601f168201915b50505050509450965096509650965096509650505b9499939850945094509450565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610efb57600080fd5b505afa158015610f0f573d6000803e3d6000fd5b505050506040513d6020811015610f2557600080fd5b50516001600160a01b03163314610f7a5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60088310610fd25760408051600160e51b62461bcd02815260206004820152601460248201527f696e76616c6964206163636573732076616c7565000000000000000000000000604482015290519081900360640190fd5b600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561101c578181015183820152602001611004565b50505050905090810190601f1680156110495780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561107c578181015183820152602001611064565b50505050905090810190601f1680156110a95780820380516001836020036101000a031916815260200191505b5094505050505060405160208183030381529060405280519060200120815260200190815260200160002054600014151561112e5760408051600160e51b62461bcd02815260206004820152601760248201527f726f6c652065786973747320666f7220746865206f7267000000000000000000604482015290519081900360640190fd5b60038054600101908190556040805160208082018381528951606084015289516002946000948c948c94938493830192608001918701908083838b5b8381101561118257818101518382015260200161116a565b50505050905090810190601f1680156111af5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156111e25781810151838201526020016111ca565b50505050905090810190601f16801561120f5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208852878201989098529587016000908120989098555050845160c0810186528b81528085018b905294850189905250505084151560608301528315156080830152600160a083018190528054808201808355919094528251805191946004027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601926112b9928492909101906127b3565b5060208281015180516112d292600185019201906127b3565b5060408281015160028301556060808401516003909301805460808087015160a09788015160ff199093169615159690961761ff001916610100961515969096029590951762ff0000191662010000911515919091021790558151918201889052861515908201528415159181019190915281815287519181019190915286517fefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c92508791879187918791879190819060208083019160c08401918a019080838360005b838110156113ae578181015183820152602001611396565b50505050905090810190601f1680156113db5780820380516001836020036101000a031916815260200191505b50838103825287518152875160209182019189019080838360005b8381101561140e5781810151838201526020016113f6565b50505050905090810190601f16801561143b5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15050505050565b6001545b90565b60608060008060008060018781548110151561147357fe5b906000526020600020906004020160000160018881548110151561149357fe5b90600052602060002090600402016001016001898154811015156114b357fe5b90600052602060002090600402016002015460018a8154811015156114d457fe5b60009182526020909120600360049092020101546001805460ff909216918c9081106114fc57fe5b906000526020600020906004020160030160019054906101000a900460ff1660018c81548110151561152a57fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff1692909188918301828280156115cf5780601f106115a4576101008083540402835291602001916115cf565b820191906000526020600020905b8154815290600101906020018083116115b257829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a94509250840190508282801561165d5780601f106116325761010080835404028352916020019161165d565b820191906000526020600020905b81548152906001019060200180831161164057829003601f168201915b5050505050945095509550955095509550955091939550919395565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156116c657600080fd5b505afa1580156116da573d6000803e3d6000fd5b505050506040513d60208110156116f057600080fd5b50516001600160a01b031633146117455760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000858585856040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515156118315760408051600160e51b62461bcd02815260206004820152601360248201527f726f6c6520646f6573206e6f7420657869737400000000000000000000000000604482015290519081900360640190fd5b60006118a685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f890181900481028201810190925287815292508791508690819084018382808284376000920191909152506126a392505050565b905060006001828154811015156118b957fe5b906000526020600020906004020160030160026101000a81548160ff0219169083151502179055507f1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b600080600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156119c15781810151838201526020016119a9565b50505050905090810190601f1680156119ee5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015611a21578181015183820152602001611a09565b50505050905090810190601f168015611a4e5780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611ac557611a8d85856126a3565b9050600181815481101515611a9e57fe5b906000526020600020906004020160030160029054906101000a900460ff16915050611be1565b600260008685604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611b0f578181015183820152602001611af7565b50505050905090810190601f168015611b3c5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015611b6f578181015183820152602001611b57565b50505050905090810190601f168015611b9c5780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611bdb57611a8d85846126a3565b60009150505b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611c3757600080fd5b505afa158015611c4b573d6000803e3d6000fd5b505050506040513d6020811015611c6157600080fd5b50516001600160a01b03163314611cb65760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611d5d87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061197492505050565b1515611d6b57506000611f5e565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611e8257611e7b88888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a91508990819084018382808284376000920191909152506126a392505050565b9050611ef8565b611ef588888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506126a392505050565b90505b6001805482908110611f0657fe5b906000526020600020906004020160030160029054906101000a900460ff168015611f5a57506001805482908110611f3a57fe5b906000526020600020906004020160030160019054906101000a900460ff165b9150505b9695505050505050565b600080600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611fb5578181015183820152602001611f9d565b50505050905090810190601f168015611fe25780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015612015578181015183820152602001611ffd565b50505050905090810190601f1680156120425780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415156120ac5761208185856126a3565b905060018181548110151561209257fe5b906000526020600020906004020160020154915050611be1565b600260008685604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156120f65781810151838201526020016120de565b50505050905090810190601f1680156121235780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561215657818101518382015260200161213e565b50505050905090810190601f1680156121835780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611bdb5761208185846126a3565b60008061226c89898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8d018190048102820181019092528b815292508b91508a908190840183828082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a9150899081908401838280828437600092019190915250611f6892505050565b90508060031415612281576001915050612323565b8260011480156122a65750806001148061229b5750806005145b806122a65750806006145b156122b5576001915050612323565b8260021480156122da575080600214806122cf5750806006145b806122da5750806007145b156122e9576001915050612323565b82600314801561230e575080600414806123035750806005145b8061230e5750806007145b1561231d576001915050612323565b60009150505b979650505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561237d57600080fd5b505afa158015612391573d6000803e3d6000fd5b505050506040513d60208110156123a757600080fd5b50516001600160a01b031633146123fc5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6124a387878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061197492505050565b15156124b157506000611f5e565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f82011690508083019250505096505050505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415156125c8576125c188888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a91508990819084018382808284376000920191909152506126a392505050565b905061263e565b61263b88888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506126a392505050565b90505b600180548290811061264c57fe5b906000526020600020906004020160030160029054906101000a900460ff168015611f5a5750600180548290811061268057fe5b600091825260209091206004909102016003015460ff1698975050505050505050565b60006001600260008585604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156126f15781810151838201526020016126d9565b50505050905090810190601f16801561271e5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015612751578181015183820152602001612739565b50505050905090810190601f16801561277e5780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205403905092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106127f457805160ff1916838001178555612821565b82800160010185558215612821579182015b82811115612821578251825591602001919060010190612806565b5061282d929150612831565b5090565b61145891905b8082111561282d576000815560010161283756fea165627a7a7230582075c8fd5504ba38b54f31bd0f3c28ac8ef88060c8e359bff58088d242f8cd256c0029" + +// DeployRoleManager deploys a new Ethereum contract, binding an instance of RoleManager to it. +func DeployRoleManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *RoleManager, error) { + parsed, err := abi.JSON(strings.NewReader(RoleManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(RoleManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &RoleManager{RoleManagerCaller: RoleManagerCaller{contract: contract}, RoleManagerTransactor: RoleManagerTransactor{contract: contract}, RoleManagerFilterer: RoleManagerFilterer{contract: contract}}, nil +} + +// RoleManager is an auto generated Go binding around an Ethereum contract. +type RoleManager struct { + RoleManagerCaller // Read-only binding to the contract + RoleManagerTransactor // Write-only binding to the contract + RoleManagerFilterer // Log filterer for contract events +} + +// RoleManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type RoleManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RoleManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type RoleManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RoleManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type RoleManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RoleManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type RoleManagerSession struct { + Contract *RoleManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RoleManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type RoleManagerCallerSession struct { + Contract *RoleManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// RoleManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type RoleManagerTransactorSession struct { + Contract *RoleManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RoleManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type RoleManagerRaw struct { + Contract *RoleManager // Generic contract binding to access the raw methods on +} + +// RoleManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type RoleManagerCallerRaw struct { + Contract *RoleManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// RoleManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type RoleManagerTransactorRaw struct { + Contract *RoleManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewRoleManager creates a new instance of RoleManager, bound to a specific deployed contract. +func NewRoleManager(address common.Address, backend bind.ContractBackend) (*RoleManager, error) { + contract, err := bindRoleManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &RoleManager{RoleManagerCaller: RoleManagerCaller{contract: contract}, RoleManagerTransactor: RoleManagerTransactor{contract: contract}, RoleManagerFilterer: RoleManagerFilterer{contract: contract}}, nil +} + +// NewRoleManagerCaller creates a new read-only instance of RoleManager, bound to a specific deployed contract. +func NewRoleManagerCaller(address common.Address, caller bind.ContractCaller) (*RoleManagerCaller, error) { + contract, err := bindRoleManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &RoleManagerCaller{contract: contract}, nil +} + +// NewRoleManagerTransactor creates a new write-only instance of RoleManager, bound to a specific deployed contract. +func NewRoleManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*RoleManagerTransactor, error) { + contract, err := bindRoleManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &RoleManagerTransactor{contract: contract}, nil +} + +// NewRoleManagerFilterer creates a new log filterer instance of RoleManager, bound to a specific deployed contract. +func NewRoleManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*RoleManagerFilterer, error) { + contract, err := bindRoleManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &RoleManagerFilterer{contract: contract}, nil +} + +// bindRoleManager binds a generic wrapper to an already deployed contract. +func bindRoleManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(RoleManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_RoleManager *RoleManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _RoleManager.Contract.RoleManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_RoleManager *RoleManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _RoleManager.Contract.RoleManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_RoleManager *RoleManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _RoleManager.Contract.RoleManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_RoleManager *RoleManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _RoleManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_RoleManager *RoleManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _RoleManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_RoleManager *RoleManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _RoleManager.Contract.contract.Transact(opts, method, params...) +} + +// GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. +// +// Solidity: function getNumberOfRoles() constant returns(uint256) +func (_RoleManager *RoleManagerCaller) GetNumberOfRoles(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "getNumberOfRoles") + return *ret0, err +} + +// GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. +// +// Solidity: function getNumberOfRoles() constant returns(uint256) +func (_RoleManager *RoleManagerSession) GetNumberOfRoles() (*big.Int, error) { + return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) +} + +// GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. +// +// Solidity: function getNumberOfRoles() constant returns(uint256) +func (_RoleManager *RoleManagerCallerSession) GetNumberOfRoles() (*big.Int, error) { + return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) +} + +// GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. +// +// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + ret := new(struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool + }) + out := ret + err := _RoleManager.contract.Call(opts, out, "getRoleDetails", _roleId, _orgId) + return *ret, err +} + +// GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. +// +// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerSession) GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetails(&_RoleManager.CallOpts, _roleId, _orgId) +} + +// GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. +// +// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCallerSession) GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetails(&_RoleManager.CallOpts, _roleId, _orgId) +} + +// GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. +// +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOpts, _rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + ret := new(struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool + }) + out := ret + err := _RoleManager.contract.Call(opts, out, "getRoleDetailsFromIndex", _rIndex) + return *ret, err +} + +// GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. +// +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetailsFromIndex(&_RoleManager.CallOpts, _rIndex) +} + +// GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. +// +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +func (_RoleManager *RoleManagerCallerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return _RoleManager.Contract.GetRoleDetailsFromIndex(&_RoleManager.CallOpts, _rIndex) +} + +// IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. +// +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCaller) IsAdminRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "isAdminRole", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. +// +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. +// +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. +// +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCaller) IsVoterRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "isVoterRole", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. +// +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. +// +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// RoleAccess is a free data retrieval call binding the contract method 0xcfc83dfa. +// +// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) constant returns(uint256) +func (_RoleManager *RoleManagerCaller) RoleAccess(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "roleAccess", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// RoleAccess is a free data retrieval call binding the contract method 0xcfc83dfa. +// +// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) constant returns(uint256) +func (_RoleManager *RoleManagerSession) RoleAccess(_roleId string, _orgId string, _ultParent string) (*big.Int, error) { + return _RoleManager.Contract.RoleAccess(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// RoleAccess is a free data retrieval call binding the contract method 0xcfc83dfa. +// +// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) constant returns(uint256) +func (_RoleManager *RoleManagerCallerSession) RoleAccess(_roleId string, _orgId string, _ultParent string) (*big.Int, error) { + return _RoleManager.Contract.RoleAccess(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// RoleExists is a free data retrieval call binding the contract method 0xabf5739f. +// +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCaller) RoleExists(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "roleExists", _roleId, _orgId, _ultParent) + return *ret0, err +} + +// RoleExists is a free data retrieval call binding the contract method 0xabf5739f. +// +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// RoleExists is a free data retrieval call binding the contract method 0xabf5739f. +// +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { + return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0xd1f77866. +// +// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) constant returns(bool) +func (_RoleManager *RoleManagerCaller) TransactionAllowed(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string, _typeOfTxn *big.Int) (bool, error) { + var ( + ret0 = new(bool) + ) + out := ret0 + err := _RoleManager.contract.Call(opts, out, "transactionAllowed", _roleId, _orgId, _ultParent, _typeOfTxn) + return *ret0, err +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0xd1f77866. +// +// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) constant returns(bool) +func (_RoleManager *RoleManagerSession) TransactionAllowed(_roleId string, _orgId string, _ultParent string, _typeOfTxn *big.Int) (bool, error) { + return _RoleManager.Contract.TransactionAllowed(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent, _typeOfTxn) +} + +// TransactionAllowed is a free data retrieval call binding the contract method 0xd1f77866. +// +// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) constant returns(bool) +func (_RoleManager *RoleManagerCallerSession) TransactionAllowed(_roleId string, _orgId string, _ultParent string, _typeOfTxn *big.Int) (bool, error) { + return _RoleManager.Contract.TransactionAllowed(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent, _typeOfTxn) +} + +// AddRole is a paid mutator transaction binding the contract method 0x7b713579. +// +// Solidity: function addRole(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) returns() +func (_RoleManager *RoleManagerTransactor) AddRole(opts *bind.TransactOpts, _roleId string, _orgId string, _baseAccess *big.Int, _isVoter bool, _isAdmin bool) (*types.Transaction, error) { + return _RoleManager.contract.Transact(opts, "addRole", _roleId, _orgId, _baseAccess, _isVoter, _isAdmin) +} + +// AddRole is a paid mutator transaction binding the contract method 0x7b713579. +// +// Solidity: function addRole(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) returns() +func (_RoleManager *RoleManagerSession) AddRole(_roleId string, _orgId string, _baseAccess *big.Int, _isVoter bool, _isAdmin bool) (*types.Transaction, error) { + return _RoleManager.Contract.AddRole(&_RoleManager.TransactOpts, _roleId, _orgId, _baseAccess, _isVoter, _isAdmin) +} + +// AddRole is a paid mutator transaction binding the contract method 0x7b713579. +// +// Solidity: function addRole(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) returns() +func (_RoleManager *RoleManagerTransactorSession) AddRole(_roleId string, _orgId string, _baseAccess *big.Int, _isVoter bool, _isAdmin bool) (*types.Transaction, error) { + return _RoleManager.Contract.AddRole(&_RoleManager.TransactOpts, _roleId, _orgId, _baseAccess, _isVoter, _isAdmin) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_RoleManager *RoleManagerTransactor) RemoveRole(opts *bind.TransactOpts, _roleId string, _orgId string) (*types.Transaction, error) { + return _RoleManager.contract.Transact(opts, "removeRole", _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_RoleManager *RoleManagerSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _RoleManager.Contract.RemoveRole(&_RoleManager.TransactOpts, _roleId, _orgId) +} + +// RemoveRole is a paid mutator transaction binding the contract method 0xa6343012. +// +// Solidity: function removeRole(string _roleId, string _orgId) returns() +func (_RoleManager *RoleManagerTransactorSession) RemoveRole(_roleId string, _orgId string) (*types.Transaction, error) { + return _RoleManager.Contract.RemoveRole(&_RoleManager.TransactOpts, _roleId, _orgId) +} + +// RoleManagerRoleCreatedIterator is returned from FilterRoleCreated and is used to iterate over the raw logs and unpacked data for RoleCreated events raised by the RoleManager contract. +type RoleManagerRoleCreatedIterator struct { + Event *RoleManagerRoleCreated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *RoleManagerRoleCreatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleCreated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleCreated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *RoleManagerRoleCreatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *RoleManagerRoleCreatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// RoleManagerRoleCreated represents a RoleCreated event raised by the RoleManager contract. +type RoleManagerRoleCreated struct { + RoleId string + OrgId string + BaseAccess *big.Int + IsVoter bool + IsAdmin bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleCreated is a free log retrieval operation binding the contract event 0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c. +// +// Solidity: event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) +func (_RoleManager *RoleManagerFilterer) FilterRoleCreated(opts *bind.FilterOpts) (*RoleManagerRoleCreatedIterator, error) { + + logs, sub, err := _RoleManager.contract.FilterLogs(opts, "RoleCreated") + if err != nil { + return nil, err + } + return &RoleManagerRoleCreatedIterator{contract: _RoleManager.contract, event: "RoleCreated", logs: logs, sub: sub}, nil +} + +var RoleCreatedTopicHash = "0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c" + +// WatchRoleCreated is a free log subscription operation binding the contract event 0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c. +// +// Solidity: event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) +func (_RoleManager *RoleManagerFilterer) WatchRoleCreated(opts *bind.WatchOpts, sink chan<- *RoleManagerRoleCreated) (event.Subscription, error) { + + logs, sub, err := _RoleManager.contract.WatchLogs(opts, "RoleCreated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(RoleManagerRoleCreated) + if err := _RoleManager.contract.UnpackLog(event, "RoleCreated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleCreated is a log parse operation binding the contract event 0xefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c. +// +// Solidity: event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, bool _isVoter, bool _isAdmin) +func (_RoleManager *RoleManagerFilterer) ParseRoleCreated(log types.Log) (*RoleManagerRoleCreated, error) { + event := new(RoleManagerRoleCreated) + if err := _RoleManager.contract.UnpackLog(event, "RoleCreated", log); err != nil { + return nil, err + } + return event, nil +} + +// RoleManagerRoleRevokedIterator is returned from FilterRoleRevoked and is used to iterate over the raw logs and unpacked data for RoleRevoked events raised by the RoleManager contract. +type RoleManagerRoleRevokedIterator struct { + Event *RoleManagerRoleRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *RoleManagerRoleRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(RoleManagerRoleRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *RoleManagerRoleRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *RoleManagerRoleRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// RoleManagerRoleRevoked represents a RoleRevoked event raised by the RoleManager contract. +type RoleManagerRoleRevoked struct { + RoleId string + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleRevoked is a free log retrieval operation binding the contract event 0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6. +// +// Solidity: event RoleRevoked(string _roleId, string _orgId) +func (_RoleManager *RoleManagerFilterer) FilterRoleRevoked(opts *bind.FilterOpts) (*RoleManagerRoleRevokedIterator, error) { + + logs, sub, err := _RoleManager.contract.FilterLogs(opts, "RoleRevoked") + if err != nil { + return nil, err + } + return &RoleManagerRoleRevokedIterator{contract: _RoleManager.contract, event: "RoleRevoked", logs: logs, sub: sub}, nil +} + +var RoleRevokedTopicHash = "0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6" + +// WatchRoleRevoked is a free log subscription operation binding the contract event 0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6. +// +// Solidity: event RoleRevoked(string _roleId, string _orgId) +func (_RoleManager *RoleManagerFilterer) WatchRoleRevoked(opts *bind.WatchOpts, sink chan<- *RoleManagerRoleRevoked) (event.Subscription, error) { + + logs, sub, err := _RoleManager.contract.WatchLogs(opts, "RoleRevoked") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(RoleManagerRoleRevoked) + if err := _RoleManager.contract.UnpackLog(event, "RoleRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleRevoked is a log parse operation binding the contract event 0x1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6. +// +// Solidity: event RoleRevoked(string _roleId, string _orgId) +func (_RoleManager *RoleManagerFilterer) ParseRoleRevoked(log types.Log) (*RoleManagerRoleRevoked, error) { + event := new(RoleManagerRoleRevoked) + if err := _RoleManager.contract.UnpackLog(event, "RoleRevoked", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v2/bind/voter.go b/permission/v2/bind/voter.go new file mode 100644 index 0000000000..af2ba5230e --- /dev/null +++ b/permission/v2/bind/voter.go @@ -0,0 +1,853 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package permission + +import ( + "github.com/ethereum/go-ethereum/common/math" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = math.U256Bytes + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// VoterManagerABI is the input ABI used to generate the binding from. +const VoterManagerABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"getPendingOpDetails\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"addVoter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"deleteVoter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_authOrg\",\"type\":\"string\"},{\"name\":\"_vAccount\",\"type\":\"address\"},{\"name\":\"_pendingOp\",\"type\":\"uint256\"}],\"name\":\"processVote\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_authOrg\",\"type\":\"string\"},{\"name\":\"_orgId\",\"type\":\"string\"},{\"name\":\"_enodeId\",\"type\":\"string\"},{\"name\":\"_account\",\"type\":\"address\"},{\"name\":\"_pendingOp\",\"type\":\"uint256\"}],\"name\":\"addVotingItem\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_permUpgradable\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"VoterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_vAccount\",\"type\":\"address\"}],\"name\":\"VoterDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"VotingItemAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_orgId\",\"type\":\"string\"}],\"name\":\"VoteProcessed\",\"type\":\"event\"}]" + +var VoterManagerParsedABI, _ = abi.JSON(strings.NewReader(VoterManagerABI)) + +// VoterManagerBin is the compiled bytecode used for deploying new contracts. +var VoterManagerBin = "0x6080604052600060035534801561001557600080fd5b50604051602080611fe48339810180604052602081101561003557600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055611f7d806100676000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063014e6acc1461005c5780635607395b146101c857806359cbd6fe14610241578063b0213864146102b8578063e98ac22d14610349575b600080fd5b6100ca6004803603602081101561007257600080fd5b810190602081018135600160201b81111561008c57600080fd5b82018360208201111561009e57600080fd5b803590602001918460018302840111600160201b831117156100bf57600080fd5b509092509050610466565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015610129578181015183820152602001610111565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015610189578181015183820152602001610171565b50505050905090810190601f1680156101b65780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61023f600480360360408110156101de57600080fd5b810190602081018135600160201b8111156101f857600080fd5b82018360208201111561020a57600080fd5b803590602001918460018302840111600160201b8311171561022b57600080fd5b9193509150356001600160a01b0316610740565b005b61023f6004803603604081101561025757600080fd5b810190602081018135600160201b81111561027157600080fd5b82018360208201111561028357600080fd5b803590602001918460018302840111600160201b831117156102a457600080fd5b9193509150356001600160a01b0316610f06565b610335600480360360608110156102ce57600080fd5b810190602081018135600160201b8111156102e857600080fd5b8201836020820111156102fa57600080fd5b803590602001918460018302840111600160201b8311171561031b57600080fd5b91935091506001600160a01b0381351690602001356111e8565b604080519115158252519081900360200190f35b61023f600480360360a081101561035f57600080fd5b810190602081018135600160201b81111561037957600080fd5b82018360208201111561038b57600080fd5b803590602001918460018302840111600160201b831117156103ac57600080fd5b919390929091602081019035600160201b8111156103c957600080fd5b8201836020820111156103db57600080fd5b803590602001918460018302840111600160201b831117156103fc57600080fd5b919390929091602081019035600160201b81111561041957600080fd5b82018360208201111561042b57600080fd5b803590602001918460018302840111600160201b8311171561044c57600080fd5b91935091506001600160a01b0381351690602001356116f8565b6060806000806000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156104b957600080fd5b505afa1580156104cd573d6000803e3d6000fd5b505050506040513d60208110156104e357600080fd5b50516001600160a01b031633146105385760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600061057987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561058a57fe5b90600052602060002090600b02016004016000016001828154811015156105ad57fe5b90600052602060002090600b02016004016001016001838154811015156105d057fe5b600091825260209091206006600b909202010154600180546001600160a01b0390921691859081106105fe57fe5b60009182526020918290206007600b909202010154845460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928691908301828280156106995780601f1061066e57610100808354040283529160200191610699565b820191906000526020600020905b81548152906001019060200180831161067c57829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156107275780601f106106fc57610100808354040283529160200191610727565b820191906000526020600020905b81548152906001019060200180831161070a57829003601f168201915b5050505050925094509450945094505092959194509250565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561078d57600080fd5b505afa1580156107a1573d6000803e3d6000fd5b505050506040513d60208110156107b757600080fd5b50516001600160a01b0316331461080c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000848460405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415610b78576003805460010190819055604080516020808201908152918101859052600291600091879187918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506000600180548091906001016109069190611cdd565b9050838360018381548110151561091957fe5b6000918252602090912061093393600b9092020191611d0e565b506001808281548110151561094457fe5b90600052602060002090600b0201600101819055506001808281548110151561096957fe5b90600052602060002090600b020160020181905550600060018281548110151561098f57fe5b90600052602060002090600b020160030181905550604051806020016040528060008152506001828154811015156109c357fe5b90600052602060002090600b020160040160000190805190602001906109ea929190611d8c565b506040805160208101909152600081526001805483908110610a0857fe5b90600052602060002090600b02016004016001019080519060200190610a2f929190611d8c565b506000600182815481101515610a4157fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b0393909316929092179091556001805483908110610a8157fe5b600091825260209091206007600b9092020101556001805482908110610aa357fe5b90600052602060002090600b020160010154600182815481101515610ac457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610afb57fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b969096029093016008018054938401815586529290942093519301805492516001600160a01b03199093169390911692909217600160a01b60ff021916600160a01b9115159190910217905550610e90565b6000610bb984848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050600181815481101515610bca57fe5b600091825260208083206001600160a01b03861684526009600b9093020191909101905260409020541515610d2c576001805482908110610c0757fe5b600091825260209091206001600b909202018101805482019055805482908110610c2d57fe5b90600052602060002090600b020160010154600182815481101515610c4e57fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610c8557fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b96909602909301600801805480850182559087529390952090519201805493516001600160a01b03199094169290941691909117600160a01b60ff021916600160a01b9215159290920291909117909155805482908110610d0d57fe5b600091825260209091206002600b909202010180546001019055610e8e565b6000610d6f85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250879250611ba5915050565b9050600182815481101515610d8057fe5b90600052602060002090600b020160080181815481101515610d9e57fe5b600091825260209091200154600160a01b900460ff16151560011415610e0e5760408051600160e51b62461bcd02815260206004820152600f60248201527f616c7265616479206120766f7465720000000000000000000000000000000000604482015290519081900360640190fd5b60018083815481101515610e1e57fe5b90600052602060002090600b020160080182815481101515610e3c57fe5b60009182526020909120018054911515600160a01b02600160a01b60ff02199092169190911790556001805483908110610e7257fe5b600091825260209091206002600b909202010180546001019055505b505b604080516001600160a01b03831660208201528181529081018390527f424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574908490849084908060608101858580828437600083820152604051601f909101601f1916909201829003965090945050505050a1505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610f5357600080fd5b505afa158015610f67573d6000803e3d6000fd5b505050506040513d6020811015610f7d57600080fd5b50516001600160a01b03163314610fd25760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250849250611016915083905082611bf7565b15156001146110645760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b60006110a586868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060006110ea87878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250899250611ba5915050565b90506001828154811015156110fb57fe5b6000918252602082206002600b90920201018054600019019055600180548490811061112357fe5b90600052602060002090600b02016008018281548110151561114157fe5b9060005260206000200160000160146101000a81548160ff0219169083151502179055507f654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b68787876040518080602001836001600160a01b03166001600160a01b031681526020018281038252858582818152602001925080828437600083820152604051601f909101601f1916909201829003965090945050505050a150505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561123757600080fd5b505afa15801561124b573d6000803e3d6000fd5b505050506040513d602081101561126157600080fd5b50516001600160a01b031633146112b65760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508692506112fa915083905082611bf7565b15156001146113485760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b61138987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250889250611ca7915050565b15156001146113e25760408051600160e51b62461bcd02815260206004820152601260248201527f6e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b600061142388888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561143457fe5b60009182526020808320848452600a600b9093020191909101815260408083206001600160a01b038a16845290915290205460ff161515600114156114c35760408051600160e51b62461bcd02815260206004820152601260248201527f63616e6e6f7420646f75626c6520766f74650000000000000000000000000000604482015290519081900360640190fd5b60018054829081106114d157fe5b600091825260209091206003600b90920201018054600190810190915580548190839081106114fc57fe5b60009182526020808320858452600b92909202909101600a01815260408083206001600160a01b038b168452825291829020805460ff19169315159390931790925580518281529182018990527f87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508918a918a919081908101848480828437600083820152604051601f909101601f19169092018290039550909350505050a160026001828154811015156115ac57fe5b90600052602060002090600b0201600201548115156115c757fe5b046001828154811015156115d757fe5b90600052602060002090600b02016003015411156116e857604080516020810190915260008152600180548390811061160c57fe5b90600052602060002090600b02016004016000019080519060200190611633929190611d8c565b50604080516020810190915260008152600180548390811061165157fe5b90600052602060002090600b02016004016001019080519060200190611678929190611d8c565b50600060018281548110151561168a57fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b03939093169290921790915560018054839081106116ca57fe5b600091825260209091206007600b90920201015550600192506116ee565b60009350505b5050949350505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561174557600080fd5b505afa158015611759573d6000803e3d6000fd5b505050506040513d602081101561176f57600080fd5b50516001600160a01b031633146117c45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b61180388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509250611ca7915050565b151561184357604051600160e51b62461bcd028152600401808060200182810382526034815260200180611f1e6034913960400191505060405180910390fd5b600061188489898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050868660018381548110151561189757fe5b90600052602060002090600b020160040160000191906118b8929190611d0e565b5084846001838154811015156118ca57fe5b90600052602060002090600b020160040160010191906118eb929190611d0e565b50826001828154811015156118fc57fe5b90600052602060002090600b020160040160020160006101000a8154816001600160a01b0302191690836001600160a01b031602179055508160018281548110151561194457fe5b6000918252602082206007600b9092020101919091555b600180548390811061196957fe5b90600052602060002090600b020160080180549050811015611a6b57600180548390811061199357fe5b90600052602060002090600b0201600801818154811015156119b157fe5b600091825260209091200154600160a01b900460ff1615611a635760006001838154811015156119dd57fe5b90600052602060002090600b0201600a0160008481526020019081526020016000206000600185815481101515611a1057fe5b90600052602060002090600b020160080184815481101515611a2e57fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff19169115159190911790555b60010161195b565b506000600182815481101515611a7d57fe5b90600052602060002090600b0201600301819055507f5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3898960405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a1505050505050505050565b6000600160026000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611b46578181015183820152602001611b2e565b50505050905090810190601f168015611b735780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b600080611bb184611afd565b905060018082815481101515611bc357fe5b600091825260208083206001600160a01b03881684526009600b909302019190910190526040902054039150505b92915050565b600080611c0384611afd565b9050600181815481101515611c1457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020541515611c4d576000915050611bf1565b6000611c598585611ba5565b9050600182815481101515611c6a57fe5b90600052602060002090600b020160080181815481101515611c8857fe5b600091825260209091200154600160a01b900460ff1695945050505050565b6000816001611cb585611afd565b81548110611cbf57fe5b90600052602060002090600b02016004016003015414905092915050565b815481835581811115611d0957600b0281600b028360005260206000209182019101611d099190611dfa565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611d4f5782800160ff19823516178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578235825591602001919060010190611d61565b50611d88929150611e7f565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611dcd57805160ff1916838001178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578251825591602001919060010190611ddf565b611e7c91905b80821115611d88576000611e148282611e99565b60006001830181905560028301819055600383018190556004830190611e3a8282611e99565b611e48600183016000611e99565b506002810180546001600160a01b031916905560006003909101819055611e73906008840190611ee0565b50600b01611e00565b90565b611e7c91905b80821115611d885760008155600101611e85565b50805460018160011615610100020316600290046000825580601f10611ebf5750611edd565b601f016020900490600052602060002090810190611edd9190611e7f565b50565b5080546000825590600052602060002090810190611edd9190611e7c91905b80821115611d885780546001600160a81b0319168155600101611eff56fe6974656d732070656e64696e6720666f7220617070726f76616c2e206e6577206974656d2063616e6e6f74206265206164646564a165627a7a723058207dc9a68c6931494f043f7420dfa8288733d9a3a676ed30b4ac8e9cc704bd928b0029" + +// DeployVoterManager deploys a new Ethereum contract, binding an instance of VoterManager to it. +func DeployVoterManager(auth *bind.TransactOpts, backend bind.ContractBackend, _permUpgradable common.Address) (common.Address, *types.Transaction, *VoterManager, error) { + parsed, err := abi.JSON(strings.NewReader(VoterManagerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(VoterManagerBin), backend, _permUpgradable) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &VoterManager{VoterManagerCaller: VoterManagerCaller{contract: contract}, VoterManagerTransactor: VoterManagerTransactor{contract: contract}, VoterManagerFilterer: VoterManagerFilterer{contract: contract}}, nil +} + +// VoterManager is an auto generated Go binding around an Ethereum contract. +type VoterManager struct { + VoterManagerCaller // Read-only binding to the contract + VoterManagerTransactor // Write-only binding to the contract + VoterManagerFilterer // Log filterer for contract events +} + +// VoterManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type VoterManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VoterManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type VoterManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VoterManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type VoterManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// VoterManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type VoterManagerSession struct { + Contract *VoterManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// VoterManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type VoterManagerCallerSession struct { + Contract *VoterManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// VoterManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type VoterManagerTransactorSession struct { + Contract *VoterManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// VoterManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type VoterManagerRaw struct { + Contract *VoterManager // Generic contract binding to access the raw methods on +} + +// VoterManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type VoterManagerCallerRaw struct { + Contract *VoterManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// VoterManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type VoterManagerTransactorRaw struct { + Contract *VoterManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewVoterManager creates a new instance of VoterManager, bound to a specific deployed contract. +func NewVoterManager(address common.Address, backend bind.ContractBackend) (*VoterManager, error) { + contract, err := bindVoterManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &VoterManager{VoterManagerCaller: VoterManagerCaller{contract: contract}, VoterManagerTransactor: VoterManagerTransactor{contract: contract}, VoterManagerFilterer: VoterManagerFilterer{contract: contract}}, nil +} + +// NewVoterManagerCaller creates a new read-only instance of VoterManager, bound to a specific deployed contract. +func NewVoterManagerCaller(address common.Address, caller bind.ContractCaller) (*VoterManagerCaller, error) { + contract, err := bindVoterManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VoterManagerCaller{contract: contract}, nil +} + +// NewVoterManagerTransactor creates a new write-only instance of VoterManager, bound to a specific deployed contract. +func NewVoterManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*VoterManagerTransactor, error) { + contract, err := bindVoterManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VoterManagerTransactor{contract: contract}, nil +} + +// NewVoterManagerFilterer creates a new log filterer instance of VoterManager, bound to a specific deployed contract. +func NewVoterManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*VoterManagerFilterer, error) { + contract, err := bindVoterManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VoterManagerFilterer{contract: contract}, nil +} + +// bindVoterManager binds a generic wrapper to an already deployed contract. +func bindVoterManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(VoterManagerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_VoterManager *VoterManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _VoterManager.Contract.VoterManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_VoterManager *VoterManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VoterManager.Contract.VoterManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_VoterManager *VoterManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VoterManager.Contract.VoterManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_VoterManager *VoterManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _VoterManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_VoterManager *VoterManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VoterManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_VoterManager *VoterManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VoterManager.Contract.contract.Transact(opts, method, params...) +} + +// GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. +// +// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +func (_VoterManager *VoterManagerCaller) GetPendingOpDetails(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { + var ( + ret0 = new(string) + ret1 = new(string) + ret2 = new(common.Address) + ret3 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + ret2, + ret3, + } + err := _VoterManager.contract.Call(opts, out, "getPendingOpDetails", _orgId) + return *ret0, *ret1, *ret2, *ret3, err +} + +// GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. +// +// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +func (_VoterManager *VoterManagerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { + return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) +} + +// GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. +// +// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +func (_VoterManager *VoterManagerCallerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { + return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) +} + +// AddVoter is a paid mutator transaction binding the contract method 0x5607395b. +// +// Solidity: function addVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactor) AddVoter(opts *bind.TransactOpts, _orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "addVoter", _orgId, _vAccount) +} + +// AddVoter is a paid mutator transaction binding the contract method 0x5607395b. +// +// Solidity: function addVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerSession) AddVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.AddVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// AddVoter is a paid mutator transaction binding the contract method 0x5607395b. +// +// Solidity: function addVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactorSession) AddVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.AddVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// AddVotingItem is a paid mutator transaction binding the contract method 0xe98ac22d. +// +// Solidity: function addVotingItem(string _authOrg, string _orgId, string _enodeId, address _account, uint256 _pendingOp) returns() +func (_VoterManager *VoterManagerTransactor) AddVotingItem(opts *bind.TransactOpts, _authOrg string, _orgId string, _enodeId string, _account common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "addVotingItem", _authOrg, _orgId, _enodeId, _account, _pendingOp) +} + +// AddVotingItem is a paid mutator transaction binding the contract method 0xe98ac22d. +// +// Solidity: function addVotingItem(string _authOrg, string _orgId, string _enodeId, address _account, uint256 _pendingOp) returns() +func (_VoterManager *VoterManagerSession) AddVotingItem(_authOrg string, _orgId string, _enodeId string, _account common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.AddVotingItem(&_VoterManager.TransactOpts, _authOrg, _orgId, _enodeId, _account, _pendingOp) +} + +// AddVotingItem is a paid mutator transaction binding the contract method 0xe98ac22d. +// +// Solidity: function addVotingItem(string _authOrg, string _orgId, string _enodeId, address _account, uint256 _pendingOp) returns() +func (_VoterManager *VoterManagerTransactorSession) AddVotingItem(_authOrg string, _orgId string, _enodeId string, _account common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.AddVotingItem(&_VoterManager.TransactOpts, _authOrg, _orgId, _enodeId, _account, _pendingOp) +} + +// DeleteVoter is a paid mutator transaction binding the contract method 0x59cbd6fe. +// +// Solidity: function deleteVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactor) DeleteVoter(opts *bind.TransactOpts, _orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "deleteVoter", _orgId, _vAccount) +} + +// DeleteVoter is a paid mutator transaction binding the contract method 0x59cbd6fe. +// +// Solidity: function deleteVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerSession) DeleteVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.DeleteVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// DeleteVoter is a paid mutator transaction binding the contract method 0x59cbd6fe. +// +// Solidity: function deleteVoter(string _orgId, address _vAccount) returns() +func (_VoterManager *VoterManagerTransactorSession) DeleteVoter(_orgId string, _vAccount common.Address) (*types.Transaction, error) { + return _VoterManager.Contract.DeleteVoter(&_VoterManager.TransactOpts, _orgId, _vAccount) +} + +// ProcessVote is a paid mutator transaction binding the contract method 0xb0213864. +// +// Solidity: function processVote(string _authOrg, address _vAccount, uint256 _pendingOp) returns(bool) +func (_VoterManager *VoterManagerTransactor) ProcessVote(opts *bind.TransactOpts, _authOrg string, _vAccount common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.contract.Transact(opts, "processVote", _authOrg, _vAccount, _pendingOp) +} + +// ProcessVote is a paid mutator transaction binding the contract method 0xb0213864. +// +// Solidity: function processVote(string _authOrg, address _vAccount, uint256 _pendingOp) returns(bool) +func (_VoterManager *VoterManagerSession) ProcessVote(_authOrg string, _vAccount common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.ProcessVote(&_VoterManager.TransactOpts, _authOrg, _vAccount, _pendingOp) +} + +// ProcessVote is a paid mutator transaction binding the contract method 0xb0213864. +// +// Solidity: function processVote(string _authOrg, address _vAccount, uint256 _pendingOp) returns(bool) +func (_VoterManager *VoterManagerTransactorSession) ProcessVote(_authOrg string, _vAccount common.Address, _pendingOp *big.Int) (*types.Transaction, error) { + return _VoterManager.Contract.ProcessVote(&_VoterManager.TransactOpts, _authOrg, _vAccount, _pendingOp) +} + +// VoterManagerVoteProcessedIterator is returned from FilterVoteProcessed and is used to iterate over the raw logs and unpacked data for VoteProcessed events raised by the VoterManager contract. +type VoterManagerVoteProcessedIterator struct { + Event *VoterManagerVoteProcessed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVoteProcessedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoteProcessed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoteProcessed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVoteProcessedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVoteProcessedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVoteProcessed represents a VoteProcessed event raised by the VoterManager contract. +type VoterManagerVoteProcessed struct { + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVoteProcessed is a free log retrieval operation binding the contract event 0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508. +// +// Solidity: event VoteProcessed(string _orgId) +func (_VoterManager *VoterManagerFilterer) FilterVoteProcessed(opts *bind.FilterOpts) (*VoterManagerVoteProcessedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VoteProcessed") + if err != nil { + return nil, err + } + return &VoterManagerVoteProcessedIterator{contract: _VoterManager.contract, event: "VoteProcessed", logs: logs, sub: sub}, nil +} + +var VoteProcessedTopicHash = "0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508" + +// WatchVoteProcessed is a free log subscription operation binding the contract event 0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508. +// +// Solidity: event VoteProcessed(string _orgId) +func (_VoterManager *VoterManagerFilterer) WatchVoteProcessed(opts *bind.WatchOpts, sink chan<- *VoterManagerVoteProcessed) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VoteProcessed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVoteProcessed) + if err := _VoterManager.contract.UnpackLog(event, "VoteProcessed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVoteProcessed is a log parse operation binding the contract event 0x87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508. +// +// Solidity: event VoteProcessed(string _orgId) +func (_VoterManager *VoterManagerFilterer) ParseVoteProcessed(log types.Log) (*VoterManagerVoteProcessed, error) { + event := new(VoterManagerVoteProcessed) + if err := _VoterManager.contract.UnpackLog(event, "VoteProcessed", log); err != nil { + return nil, err + } + return event, nil +} + +// VoterManagerVoterAddedIterator is returned from FilterVoterAdded and is used to iterate over the raw logs and unpacked data for VoterAdded events raised by the VoterManager contract. +type VoterManagerVoterAddedIterator struct { + Event *VoterManagerVoterAdded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVoterAddedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVoterAddedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVoterAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVoterAdded represents a VoterAdded event raised by the VoterManager contract. +type VoterManagerVoterAdded struct { + OrgId string + VAccount common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVoterAdded is a free log retrieval operation binding the contract event 0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574. +// +// Solidity: event VoterAdded(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) FilterVoterAdded(opts *bind.FilterOpts) (*VoterManagerVoterAddedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VoterAdded") + if err != nil { + return nil, err + } + return &VoterManagerVoterAddedIterator{contract: _VoterManager.contract, event: "VoterAdded", logs: logs, sub: sub}, nil +} + +var VoterAddedTopicHash = "0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574" + +// WatchVoterAdded is a free log subscription operation binding the contract event 0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574. +// +// Solidity: event VoterAdded(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) WatchVoterAdded(opts *bind.WatchOpts, sink chan<- *VoterManagerVoterAdded) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VoterAdded") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVoterAdded) + if err := _VoterManager.contract.UnpackLog(event, "VoterAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVoterAdded is a log parse operation binding the contract event 0x424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574. +// +// Solidity: event VoterAdded(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) ParseVoterAdded(log types.Log) (*VoterManagerVoterAdded, error) { + event := new(VoterManagerVoterAdded) + if err := _VoterManager.contract.UnpackLog(event, "VoterAdded", log); err != nil { + return nil, err + } + return event, nil +} + +// VoterManagerVoterDeletedIterator is returned from FilterVoterDeleted and is used to iterate over the raw logs and unpacked data for VoterDeleted events raised by the VoterManager contract. +type VoterManagerVoterDeletedIterator struct { + Event *VoterManagerVoterDeleted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVoterDeletedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterDeleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVoterDeleted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVoterDeletedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVoterDeletedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVoterDeleted represents a VoterDeleted event raised by the VoterManager contract. +type VoterManagerVoterDeleted struct { + OrgId string + VAccount common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVoterDeleted is a free log retrieval operation binding the contract event 0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6. +// +// Solidity: event VoterDeleted(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) FilterVoterDeleted(opts *bind.FilterOpts) (*VoterManagerVoterDeletedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VoterDeleted") + if err != nil { + return nil, err + } + return &VoterManagerVoterDeletedIterator{contract: _VoterManager.contract, event: "VoterDeleted", logs: logs, sub: sub}, nil +} + +var VoterDeletedTopicHash = "0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6" + +// WatchVoterDeleted is a free log subscription operation binding the contract event 0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6. +// +// Solidity: event VoterDeleted(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) WatchVoterDeleted(opts *bind.WatchOpts, sink chan<- *VoterManagerVoterDeleted) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VoterDeleted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVoterDeleted) + if err := _VoterManager.contract.UnpackLog(event, "VoterDeleted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVoterDeleted is a log parse operation binding the contract event 0x654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b6. +// +// Solidity: event VoterDeleted(string _orgId, address _vAccount) +func (_VoterManager *VoterManagerFilterer) ParseVoterDeleted(log types.Log) (*VoterManagerVoterDeleted, error) { + event := new(VoterManagerVoterDeleted) + if err := _VoterManager.contract.UnpackLog(event, "VoterDeleted", log); err != nil { + return nil, err + } + return event, nil +} + +// VoterManagerVotingItemAddedIterator is returned from FilterVotingItemAdded and is used to iterate over the raw logs and unpacked data for VotingItemAdded events raised by the VoterManager contract. +type VoterManagerVotingItemAddedIterator struct { + Event *VoterManagerVotingItemAdded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VoterManagerVotingItemAddedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VoterManagerVotingItemAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VoterManagerVotingItemAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VoterManagerVotingItemAddedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VoterManagerVotingItemAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VoterManagerVotingItemAdded represents a VotingItemAdded event raised by the VoterManager contract. +type VoterManagerVotingItemAdded struct { + OrgId string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVotingItemAdded is a free log retrieval operation binding the contract event 0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3. +// +// Solidity: event VotingItemAdded(string _orgId) +func (_VoterManager *VoterManagerFilterer) FilterVotingItemAdded(opts *bind.FilterOpts) (*VoterManagerVotingItemAddedIterator, error) { + + logs, sub, err := _VoterManager.contract.FilterLogs(opts, "VotingItemAdded") + if err != nil { + return nil, err + } + return &VoterManagerVotingItemAddedIterator{contract: _VoterManager.contract, event: "VotingItemAdded", logs: logs, sub: sub}, nil +} + +var VotingItemAddedTopicHash = "0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3" + +// WatchVotingItemAdded is a free log subscription operation binding the contract event 0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3. +// +// Solidity: event VotingItemAdded(string _orgId) +func (_VoterManager *VoterManagerFilterer) WatchVotingItemAdded(opts *bind.WatchOpts, sink chan<- *VoterManagerVotingItemAdded) (event.Subscription, error) { + + logs, sub, err := _VoterManager.contract.WatchLogs(opts, "VotingItemAdded") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VoterManagerVotingItemAdded) + if err := _VoterManager.contract.UnpackLog(event, "VotingItemAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVotingItemAdded is a log parse operation binding the contract event 0x5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3. +// +// Solidity: event VotingItemAdded(string _orgId) +func (_VoterManager *VoterManagerFilterer) ParseVotingItemAdded(log types.Log) (*VoterManagerVotingItemAdded, error) { + event := new(VoterManagerVotingItemAdded) + if err := _VoterManager.contract.UnpackLog(event, "VotingItemAdded", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/permission/v2/contract.go b/permission/v2/contract.go new file mode 100644 index 0000000000..d2fc26aad9 --- /dev/null +++ b/permission/v2/contract.go @@ -0,0 +1,408 @@ +package v2 + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/permission/core" + ptype "github.com/ethereum/go-ethereum/permission/core/types" + binding "github.com/ethereum/go-ethereum/permission/v2/bind" +) + +// definitions for v2 permissions model which is aligned with eea specs + +type PermissionModelV2 struct { + ContractBackend ptype.ContractBackend + PermInterf *binding.PermInterface + PermInterfSession *binding.PermInterfaceSession +} + +type Audit struct { + Backend *PermissionModelV2 +} + +type Role struct { + Backend *PermissionModelV2 +} + +type Account struct { + Backend *PermissionModelV2 +} + +type Control struct { + Backend *PermissionModelV2 +} + +type Org struct { + Backend *PermissionModelV2 +} + +type Node struct { + Backend *PermissionModelV2 +} + +type Init struct { + Backend ptype.ContractBackend + //binding contracts + PermUpgr *binding.PermUpgr + PermInterf *binding.PermInterface + PermNode *binding.NodeManager + PermAcct *binding.AcctManager + PermRole *binding.RoleManager + PermOrg *binding.OrgManager + //sessions + PermInterfSession *binding.PermInterfaceSession + permOrgSession *binding.OrgManagerSession + permNodeSession *binding.NodeManagerSession + permRoleSession *binding.RoleManagerSession + permAcctSession *binding.AcctManagerSession +} + +func (a *Account) AssignAccountRole(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.AssignAccountRole(_args.AcctId, _args.OrgId, _args.RoleId) +} + +func (a *Account) UpdateAccountStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.UpdateAccountStatus(_args.OrgId, _args.AcctId, big.NewInt(int64(_args.Action))) +} + +func (a *Account) StartBlacklistedAccountRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.StartBlacklistedAccountRecovery(_args.OrgId, _args.AcctId) +} + +func (a *Account) ApproveBlacklistedAccountRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.ApproveBlacklistedAccountRecovery(_args.OrgId, _args.AcctId) +} + +func (a *Account) ApproveAdminRole(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.ApproveAdminRole(_args.OrgId, _args.AcctId) +} + +func (a *Account) AssignAdminRole(_args ptype.TxArgs) (*types.Transaction, error) { + return a.Backend.PermInterfSession.AssignAdminRole(_args.OrgId, _args.AcctId, _args.RoleId) +} + +func (i *Init) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { + return i.permAcctSession.GetAccountDetailsFromIndex(_aIndex) +} + +func (i *Init) GetNumberOfAccounts() (*big.Int, error) { + return i.permAcctSession.GetNumberOfAccounts() +} + +func (i *Init) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return i.permRoleSession.GetRoleDetailsFromIndex(_rIndex) +} + +func (i *Init) GetNumberOfRoles() (*big.Int, error) { + return i.permRoleSession.GetNumberOfRoles() +} + +func (i *Init) GetNumberOfOrgs() (*big.Int, error) { + return i.permOrgSession.GetNumberOfOrgs() +} + +func (i *Init) UpdateNetworkBootStatus() (*types.Transaction, error) { + return i.PermInterfSession.UpdateNetworkBootStatus() +} + +func (i *Init) AddAdminAccount(_acct common.Address) (*types.Transaction, error) { + return i.PermInterfSession.AddAdminAccount(_acct) +} + +func (i *Init) AddAdminNode(url string) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(url, i.Backend.IsRaft, i.Backend.UseDns) + if err != nil { + return nil, err + } + return i.PermInterfSession.AddAdminNode(enodeId, ip, port, raftPort) +} + +func (i *Init) SetPolicy(_nwAdminOrg string, _nwAdminRole string, _oAdminRole string) (*types.Transaction, error) { + return i.PermInterfSession.SetPolicy(_nwAdminOrg, _nwAdminRole, _oAdminRole) +} + +func (i *Init) Init(_breadth *big.Int, _depth *big.Int) (*types.Transaction, error) { + return i.PermInterfSession.Init(_breadth, _depth) +} + +func (i *Init) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { + return i.permAcctSession.GetAccountDetails(_account) +} + +func (i *Init) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (string, string, *big.Int, error) { + r, err := i.permNodeSession.GetNodeDetailsFromIndex(_nodeIndex) + if err != nil { + return "", "", big.NewInt(0), err + } + return r.OrgId, core.GetNodeUrl(r.EnodeId, r.Ip[:], r.Port, r.Raftport, i.Backend.IsRaft), r.NodeStatus, err +} + +func (i *Init) GetNumberOfNodes() (*big.Int, error) { + return i.permNodeSession.GetNumberOfNodes() +} + +func (i *Init) GetNodeDetails(enodeId string) (string, string, *big.Int, error) { + r, err := i.permNodeSession.GetNodeDetails(enodeId) + if err != nil { + return "", "", big.NewInt(0), err + } + return r.OrgId, core.GetNodeUrl(r.EnodeId, r.Ip[:], r.Port, r.Raftport, i.Backend.IsRaft), r.NodeStatus, err +} + +func (i *Init) GetRoleDetails(_roleId string, _orgId string) (struct { + RoleId string + OrgId string + AccessType *big.Int + Voter bool + Admin bool + Active bool +}, error) { + return i.permRoleSession.GetRoleDetails(_roleId, _orgId) +} + +func (i *Init) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { + return i.permOrgSession.GetSubOrgIndexes(_orgId) +} + +func (i *Init) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { + return i.permOrgSession.GetOrgInfo(_orgIndex) +} + +func (i *Init) GetNetworkBootStatus() (bool, error) { + return i.PermInterfSession.GetNetworkBootStatus() +} + +func (i *Init) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { + return i.permOrgSession.GetOrgDetails(_orgId) +} + +// This is to make sure all contract instances are ready and initialized +// +// Required to be call after standard service start lifecycle +func (i *Init) BindContracts() error { + log.Debug("permission service: binding contracts") + + err := i.bindContract() + if err != nil { + return err + } + + i.initSession() + return nil +} + +func (a *Audit) ValidatePendingOp(_authOrg, _orgId, _url string, _account common.Address, _pendingOp int64) bool { + var enodeId string + var err error + if _url != "" { + enodeId, _, _, _, err = getNodeDetails(_url, a.Backend.ContractBackend.IsRaft, a.Backend.ContractBackend.UseDns) + if err != nil { + log.Error("permission: encountered error while checking for pending operations", "err", err) + return false + } + } + pOrg, pUrl, pAcct, op, err := a.Backend.PermInterfSession.GetPendingOp(_authOrg) + return err == nil && (op.Int64() == _pendingOp && pOrg == _orgId && pUrl == enodeId && pAcct == _account) +} + +func (a *Audit) CheckPendingOp(_orgId string) bool { + _, _, _, op, err := a.Backend.PermInterfSession.GetPendingOp(_orgId) + return err == nil && op.Int64() != 0 +} + +func (c *Control) ConnectionAllowed(_enodeId, _ip string, _port, _raftPort uint16) (bool, error) { + url := core.GetNodeUrl(_enodeId, _ip, _port, _raftPort, c.Backend.ContractBackend.IsRaft) + enodeId, ip, port, _, err := getNodeDetails(url, c.Backend.ContractBackend.IsRaft, c.Backend.ContractBackend.UseDns) + if err != nil { + return false, err + } + + return c.Backend.PermInterfSession.ConnectionAllowed(enodeId, ip, port) +} + +func (c *Control) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte, _transactionType core.TransactionType) error { + if allowed, err := c.Backend.PermInterfSession.TransactionAllowed(_sender, _target, _value, _gasPrice, _gasLimit, _payload); err != nil { + return err + } else if !allowed { + return ptype.ErrNoPermissionForTxn + } + return nil +} + +func (r *Role) RemoveRole(_args ptype.TxArgs) (*types.Transaction, error) { + return r.Backend.PermInterfSession.RemoveRole(_args.RoleId, _args.OrgId) +} + +func (r *Role) AddNewRole(_args ptype.TxArgs) (*types.Transaction, error) { + if _args.AccessType > 7 { + return nil, fmt.Errorf("invalid access type given") + } + return r.Backend.PermInterfSession.AddNewRole(_args.RoleId, _args.OrgId, big.NewInt(int64(_args.AccessType)), _args.IsVoter, _args.IsAdmin) +} + +func (o *Org) ApproveOrgStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.ApproveOrgStatus(_args.OrgId, big.NewInt(int64(_args.Action))) +} + +func (o *Org) UpdateOrgStatus(_args ptype.TxArgs) (*types.Transaction, error) { + return o.Backend.PermInterfSession.UpdateOrgStatus(_args.OrgId, big.NewInt(int64(_args.Action))) +} + +func (o *Org) ApproveOrg(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, o.Backend.ContractBackend.IsRaft, o.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + return o.Backend.PermInterfSession.ApproveOrg(_args.OrgId, enodeId, ip, port, raftPort, _args.AcctId) +} + +func (o *Org) AddSubOrg(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, o.Backend.ContractBackend.IsRaft, o.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + return o.Backend.PermInterfSession.AddSubOrg(_args.POrgId, _args.OrgId, enodeId, ip, port, raftPort) +} + +func (o *Org) AddOrg(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, o.Backend.ContractBackend.IsRaft, o.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + return o.Backend.PermInterfSession.AddOrg(_args.OrgId, enodeId, ip, port, raftPort, _args.AcctId) +} + +func (n *Node) ApproveBlacklistedNodeRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, n.Backend.ContractBackend.IsRaft, n.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + return n.Backend.PermInterfSession.ApproveBlacklistedNodeRecovery(_args.OrgId, enodeId, ip, port, raftPort) +} + +func (n *Node) StartBlacklistedNodeRecovery(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, n.Backend.ContractBackend.IsRaft, n.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + return n.Backend.PermInterfSession.StartBlacklistedNodeRecovery(_args.OrgId, enodeId, ip, port, raftPort) +} + +func (n *Node) AddNode(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, n.Backend.ContractBackend.IsRaft, n.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + + return n.Backend.PermInterfSession.AddNode(_args.OrgId, enodeId, ip, port, raftPort) +} + +func (n *Node) UpdateNodeStatus(_args ptype.TxArgs) (*types.Transaction, error) { + enodeId, ip, port, raftPort, err := getNodeDetails(_args.Url, n.Backend.ContractBackend.IsRaft, n.Backend.ContractBackend.UseDns) + if err != nil { + return nil, err + } + return n.Backend.PermInterfSession.UpdateNodeStatus(_args.OrgId, enodeId, ip, port, raftPort, big.NewInt(int64(_args.Action))) +} + +func (i *Init) bindContract() error { + if err := ptype.BindContract(&i.PermUpgr, func() (interface{}, error) { + return binding.NewPermUpgr(i.Backend.PermConfig.UpgrdAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermInterf, func() (interface{}, error) { + return binding.NewPermInterface(i.Backend.PermConfig.InterfAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermAcct, func() (interface{}, error) { + return binding.NewAcctManager(i.Backend.PermConfig.AccountAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermNode, func() (interface{}, error) { + return binding.NewNodeManager(i.Backend.PermConfig.NodeAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermRole, func() (interface{}, error) { + return binding.NewRoleManager(i.Backend.PermConfig.RoleAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + if err := ptype.BindContract(&i.PermOrg, func() (interface{}, error) { + return binding.NewOrgManager(i.Backend.PermConfig.OrgAddress, i.Backend.EthClnt) + }); err != nil { + return err + } + return nil +} + +func (i *Init) initSession() { + auth := bind.NewKeyedTransactor(i.Backend.Key) + log.Debug("NodeAccount V2", "nodeAcc", auth.From) + i.PermInterfSession = &binding.PermInterfaceSession{ + Contract: i.PermInterf, + CallOpts: bind.CallOpts{ + Pending: true, + }, + TransactOpts: bind.TransactOpts{ + From: auth.From, + Signer: auth.Signer, + GasLimit: 47000000, + GasPrice: big.NewInt(0), + }, + } + + i.permOrgSession = &binding.OrgManagerSession{ + Contract: i.PermOrg, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + + i.permNodeSession = &binding.NodeManagerSession{ + Contract: i.PermNode, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + + //populate roles + i.permRoleSession = &binding.RoleManagerSession{ + Contract: i.PermRole, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } + + //populate accounts + i.permAcctSession = &binding.AcctManagerSession{ + Contract: i.PermAcct, + CallOpts: bind.CallOpts{ + Pending: true, + }, + } +} + +// checks if the passed URL is no nil and then calls GetNodeDetails +func getNodeDetails(url string, isRaft, useDns bool) (string, string, uint16, uint16, error) { + if len(url) > 0 { + return ptype.GetNodeDetails(url, isRaft, useDns) + } + + return "", "", uint16(0), uint16(0), nil +} diff --git a/permission/v2/contract/AccountManager.sol b/permission/v2/contract/AccountManager.sol new file mode 100644 index 0000000000..0baaa0b59e --- /dev/null +++ b/permission/v2/contract/AccountManager.sol @@ -0,0 +1,377 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; + +/** @title Account manager contract + * @notice This contract holds implementation logic for all account management + functionality. This can be called only by the implementation contract only. + there are few view functions exposed as public and can be called directly. + these are invoked by quorum for populating permissions data in cache + * @dev account status is denoted by a fixed integer value. The values are + as below: + 0 - Not in list + 1 - Account pending approval + 2 - Active + 3 - Inactive + 4 - Suspended + 5 - Blacklisted + 6 - Revoked + 7 - Recovery Initiated for blacklisted accounts and pending approval + from network admins + Once the account is blacklisted no further activity on the account is + possible. + When adding a new org admin account to an existing org, the existing org + admin account will be in revoked status and can be assigned a new role + later + */ +contract AccountManager { + PermissionsUpgradable private permUpgradable; + struct AccountAccessDetails { + address account; + string orgId; + string role; + uint status; + bool orgAdmin; + } + + AccountAccessDetails[] private accountAccessList; + mapping(address => uint) private accountIndex; + uint private numAccounts; + + string private adminRole; + string private orgAdminRole; + + mapping(bytes32 => address) private orgAdminIndex; + + // account permission events + event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint _status); + event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin); + event AccountStatusChanged(address _account, string _orgId, uint _status); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if the account is exists and belongs to the org id passed + * @param _orgId - org id + * @param _account - account id + */ + modifier accountExists(string memory _orgId, address _account) { + require((accountIndex[_account]) != 0, "account does not exists"); + require(keccak256(abi.encode(accountAccessList[_getAccountIndex(_account)].orgId)) == keccak256(abi.encode(_orgId)), "account in different org"); + _; + } + + /// @notice constructor. sets the permissions upgradable address + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + + /** @notice returns the account details for a given account + * @param _account account id + * @return account id + * @return org id of the account + * @return role linked to the account + * @return status of the account + * @return bool indicating if the account is an org admin + */ + function getAccountDetails(address _account) external view returns (address, + string memory, string memory, uint, bool){ + if (accountIndex[_account] == 0) { + return (_account, "NONE", "", 0, false); + } + uint aIndex = _getAccountIndex(_account); + return (accountAccessList[aIndex].account, accountAccessList[aIndex].orgId, + accountAccessList[aIndex].role, accountAccessList[aIndex].status, + accountAccessList[aIndex].orgAdmin); + } + + /** @notice returns the account details for a given account if account is valid/active + * @param _account account id + * @return org id of the account + * @return role linked to the account + */ + function getAccountOrgRole(address _account) external view + returns (string memory, string memory){ + if (accountIndex[_account] == 0) { + return ("NONE", ""); + } + uint aIndex = _getAccountIndex(_account); + return (accountAccessList[aIndex].orgId, accountAccessList[aIndex].role); + } + + /** @notice returns the account details a given account index + * @param _aIndex account index + * @return account id + * @return org id of the account + * @return role linked to the account + * @return status of the account + * @return bool indicating if the account is an org admin + */ + function getAccountDetailsFromIndex(uint _aIndex) external view returns + (address, string memory, string memory, uint, bool) { + return (accountAccessList[_aIndex].account, + accountAccessList[_aIndex].orgId, accountAccessList[_aIndex].role, + accountAccessList[_aIndex].status, accountAccessList[_aIndex].orgAdmin); + } + + /** @notice returns the total number of accounts + * @return total number accounts + */ + function getNumberOfAccounts() external view returns (uint) { + return accountAccessList.length; + } + + /** @notice this is called at the time of network initialization to set + the default values of network admin and org admin roles + */ + function setDefaults(string calldata _nwAdminRole, string calldata _oAdminRole) + external onlyImplementation { + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + } + + /** @notice this function is called to assign the org admin or network + admin roles only to the passed account + * @param _account - account id + * @param _orgId - org to which it belongs + * @param _roleId - role id to be assigned + * @param _status - account status to be assigned + */ + function assignAdminRole(address _account, string calldata _orgId, + string calldata _roleId, uint _status) external onlyImplementation { + require(((keccak256(abi.encode(_roleId)) == keccak256(abi.encode(orgAdminRole))) || + (keccak256(abi.encode(_roleId)) == keccak256(abi.encode(adminRole)))), + "can be called to assign admin roles only"); + + _setAccountRole(_account, _orgId, _roleId, _status, true); + + } + + /** @notice this function is called to assign the any role to the passed + account. + * @param _account - account id + * @param _orgId - org to which it belongs + * @param _roleId - role id to be assigned + * @param _adminRole - indicates of the role is an admin role + */ + function assignAccountRole(address _account, string calldata _orgId, + string calldata _roleId, bool _adminRole) external onlyImplementation { + require(((keccak256(abi.encode(_roleId)) != keccak256(abi.encode(adminRole))) + && (keccak256(abi.encode(abi.encode(_roleId))) != keccak256(abi.encode(orgAdminRole)))), + "cannot be called fro assigning org admin and network admin roles"); + _setAccountRole(_account, _orgId, _roleId, 2, _adminRole); + } + + /** @notice this function removes existing admin account. will be called at + the time of adding a new account as org admin account. at org + level there can be one org admin account only + * @param _orgId - org id + * @return bool to indicate if voter update is required or not + * @return _adminRole - indicates of the role is an admin role + */ + function removeExistingAdmin(string calldata _orgId) external + onlyImplementation + returns (bool voterUpdate, address account) { + // change the status of existing org admin to revoked + if (orgAdminExists(_orgId)) { + uint id = _getAccountIndex(orgAdminIndex[keccak256(abi.encode(_orgId))]); + accountAccessList[id].status = 6; + accountAccessList[id].orgAdmin = false; + emit AccountAccessModified(accountAccessList[id].account, + accountAccessList[id].orgId, accountAccessList[id].role, + accountAccessList[id].orgAdmin, accountAccessList[id].status); + return ((keccak256(abi.encode(accountAccessList[id].role)) == keccak256(abi.encode(adminRole))), + accountAccessList[id].account); + } + return (false, address(0)); + } + + /** @notice function to add an account as network admin or org admin. + * @param _orgId - org id + * @param _account - account id + * @return bool to indicate if voter update is required or not + */ + function addNewAdmin(string calldata _orgId, address _account) external + onlyImplementation + returns (bool voterUpdate) { + // check of the account role is org admin role and status is pending + // approval. if yes update the status to approved + string memory role = getAccountRole(_account); + uint status = getAccountStatus(_account); + uint id = _getAccountIndex(_account); + if ((keccak256(abi.encode(role)) == keccak256(abi.encode(orgAdminRole))) && + (status == 1)) { + orgAdminIndex[keccak256(abi.encode(_orgId))] = _account; + } + accountAccessList[id].status = 2; + accountAccessList[id].orgAdmin = true; + emit AccountAccessModified(_account, accountAccessList[id].orgId, accountAccessList[id].role, + accountAccessList[id].orgAdmin, accountAccessList[id].status); + return (keccak256(abi.encode(accountAccessList[id].role)) == keccak256(abi.encode(adminRole))); + } + + /** @notice updates the account status to the passed status value + * @param _orgId - org id + * @param _account - account id + * @param _action - new status of the account + * @dev the following actions are allowed + 1 - Suspend the account + 2 - Reactivate a suspended account + 3 - Blacklist an account + 4 - Initiate recovery for black listed account + 5 - Complete recovery of black listed account and update status to active + */ + function updateAccountStatus(string calldata _orgId, address _account, uint _action) external + onlyImplementation + accountExists(_orgId, _account) { + require((_action > 0 && _action < 6), "invalid status change request"); + + // check if the account is org admin. if yes then do not allow any status change + require(checkOrgAdmin(_account, _orgId, "") != true, "status change not possible for org admin accounts"); + uint newStatus; + if (_action == 1) { + // for suspending an account current status should be active + require(accountAccessList[_getAccountIndex(_account)].status == 2, + "account is not in active status. operation cannot be done"); + newStatus = 4; + } + else if (_action == 2) { + // for reactivating a suspended account, current status should be suspended + require(accountAccessList[_getAccountIndex(_account)].status == 4, + "account is not in suspended status. operation cannot be done"); + newStatus = 2; + } + else if (_action == 3) { + require(accountAccessList[_getAccountIndex(_account)].status != 5, + "account is already blacklisted. operation cannot be done"); + newStatus = 5; + } + else if (_action == 4) { + require(accountAccessList[_getAccountIndex(_account)].status == 5, + "account is not blacklisted. operation cannot be done"); + newStatus = 7; + } + else if (_action == 5) { + require(accountAccessList[_getAccountIndex(_account)].status == 7, "account recovery not initiated. operation cannot be done"); + newStatus = 2; + } + + accountAccessList[_getAccountIndex(_account)].status = newStatus; + emit AccountStatusChanged(_account, _orgId, newStatus); + } + + /** @notice checks if the passed account exists and if exists does it + belong to the passed organization. + * @param _account - account id + * @param _orgId - org id + * @return bool true if the account does not exists or exists and belongs + * @return passed org + */ + function validateAccount(address _account, string calldata _orgId) external + view returns (bool){ + if (accountIndex[_account] == 0) { + return true; + } + uint256 id = _getAccountIndex(_account); + return (keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_orgId))); + } + + /** @notice checks if org admin account exists for the passed org id + * @param _orgId - org id + * @return true if the org admin account exists and is approved + */ + function orgAdminExists(string memory _orgId) public view returns (bool) { + if (orgAdminIndex[keccak256(abi.encode(_orgId))] != address(0)) { + address adminAcct = orgAdminIndex[keccak256(abi.encode(_orgId))]; + return getAccountStatus(adminAcct) == 2; + } + return false; + + } + + /** @notice returns the role id linked to the passed account + * @param _account account id + * @return role id + */ + function getAccountRole(address _account) public view returns (string memory) { + if (accountIndex[_account] == 0) { + return "NONE"; + } + uint256 acctIndex = _getAccountIndex(_account); + if (accountAccessList[acctIndex].status != 0) { + return accountAccessList[acctIndex].role; + } + else { + return "NONE"; + } + } + + /** @notice returns the account status for a given account + * @param _account account id + * @return account status + */ + function getAccountStatus(address _account) public view returns (uint256) { + if (accountIndex[_account] == 0) { + return 0; + } + uint256 aIndex = _getAccountIndex(_account); + return (accountAccessList[aIndex].status); + } + + + /** @notice checks if the account is a org admin for the passed org or + for the ultimate parent organization + * @param _account account id + * @param _orgId org id + * @param _ultParent master org id or + */ + function checkOrgAdmin(address _account, string memory _orgId, + string memory _ultParent) public view returns (bool) { + // check if the account role is network admin. If yes return success + if (keccak256(abi.encode(getAccountRole(_account))) == keccak256(abi.encode(adminRole))) { + // check of the orgid is network admin org. then return true + uint256 id = _getAccountIndex(_account); + return ((keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_orgId))) + || (keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_ultParent)))); + } + return ((orgAdminIndex[keccak256(abi.encode(_orgId))] == _account) || (orgAdminIndex[keccak256(abi.encode(_ultParent))] == _account)); + } + + /** @notice returns the index for a given account id + * @param _account account id + * @return account index + */ + function _getAccountIndex(address _account) internal view returns (uint256) { + return accountIndex[_account] - 1; + } + + /** @notice sets the account role to the passed role id and sets the status + * @param _account account id + * @param _orgId org id + * @param _status status to be set + * @param _oAdmin bool to indicate if account is org admin + */ + function _setAccountRole(address _account, string memory _orgId, + string memory _roleId, uint256 _status, bool _oAdmin) internal onlyImplementation { + // Check if account already exists + uint256 aIndex = _getAccountIndex(_account); + if (accountIndex[_account] != 0) { + accountAccessList[aIndex].role = _roleId; + accountAccessList[aIndex].status = _status; + accountAccessList[aIndex].orgAdmin = _oAdmin; + } + else { + numAccounts ++; + accountIndex[_account] = numAccounts; + accountAccessList.push(AccountAccessDetails(_account, _orgId, + _roleId, _status, _oAdmin)); + } + emit AccountAccessModified(_account, _orgId, _roleId, _oAdmin, _status); + } +} diff --git a/permission/v2/contract/NodeManager.sol b/permission/v2/contract/NodeManager.sol new file mode 100644 index 0000000000..7446ad6d79 --- /dev/null +++ b/permission/v2/contract/NodeManager.sol @@ -0,0 +1,310 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; +/** @title Node manager contract + * @notice This contract holds implementation logic for all node management + functionality. This can be called only by the implementation contract. + There are few view functions exposed as public and can be called directly. + These are invoked by quorum for populating permissions data in cache + * @dev node status is denoted by a fixed integer value. The values are + as below: + 0 - Not in list + 1 - Node pending approval + 2 - Active + 3 - Deactivated + 4 - Blacklisted + 5 - Blacklisted node recovery initiated. Once approved the node + status will be updated to Active (2) + Once the node is blacklisted no further activity on the node is + possible. + */ +contract NodeManager { + PermissionsUpgradable private permUpgradable; + + struct NodeDetails { + string enodeId; + string ip; + uint16 port; + uint16 raftPort; + string orgId; + uint256 status; + } + // use an array to store node details + // if we want to list all node one day, mapping is not capable + NodeDetails[] private nodeList; + // mapping of enode id to array index to track node + mapping(bytes32 => uint256) private nodeIdToIndex; + // mapping of enodeId to array index to track node + mapping(bytes32 => uint256) private enodeIdToIndex; + // tracking total number of nodes in network + uint256 private numberOfNodes; + + + // node permission events for new node propose + event NodeProposed(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + event NodeApproved(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + + // node permission events for node deactivation + event NodeDeactivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + + // node permission events for node activation + event NodeActivated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + + // node permission events for node blacklist + event NodeBlacklisted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + + // node permission events for initiating the recovery of blacklisted + // node + event NodeRecoveryInitiated(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + + // node permission events for completing the recovery of blacklisted + // node + event NodeRecoveryCompleted(string _enodeId, string _ip, uint16 _port, uint16 _raftport, string _orgId); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if the node exists in the network + * @param _enodeId full enode id + */ + modifier enodeExists(string memory _enodeId) { + require(enodeIdToIndex[keccak256(abi.encode(_enodeId))] != 0, + "passed enode id does not exist"); + _; + } + + /** @notice checks if the node does not exist in the network + * @param _enodeId full enode id + */ + modifier enodeDoesNotExists(string memory _enodeId) { + require(enodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0, + "passed enode id exists"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice fetches the node details given an enode id + * @param _enodeId full enode id + * @return org id + * @return enode id + * @return status of the node + */ + function getNodeDetails(string calldata enodeId) external view + returns (string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) { + if (nodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0) { + return ("", "", "", 0, 0, 0); + } + uint256 nodeIndex = _getNodeIndex(enodeId); + return (nodeList[nodeIndex].orgId, nodeList[nodeIndex].enodeId, nodeList[nodeIndex].ip, + nodeList[nodeIndex].port, nodeList[nodeIndex].raftPort, + nodeList[nodeIndex].status); + } + + /** @notice fetches the node details given the index of the enode + * @param _nodeIndex node index + * @return org id + * @return enode id + * @return ip of the node + * @return port of the node + * @return raftport of the node + * @return status of the node + */ + function getNodeDetailsFromIndex(uint256 _nodeIndex) external view + returns (string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) { + return (nodeList[_nodeIndex].orgId, nodeList[_nodeIndex].enodeId, nodeList[_nodeIndex].ip, + nodeList[_nodeIndex].port, nodeList[_nodeIndex].raftPort, + nodeList[_nodeIndex].status); + } + + /** @notice returns the total number of enodes in the network + * @return number of nodes + */ + function getNumberOfNodes() external view returns (uint256) { + return numberOfNodes; + } + + /** @notice called at the time of network initialization for adding + admin nodes + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _orgId org id to which the enode belongs + */ + function addAdminNode(string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, string memory _orgId) public + onlyImplementation + enodeDoesNotExists(_enodeId) { + numberOfNodes++; + enodeIdToIndex[keccak256(abi.encode(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _ip, _port, _raftport, _orgId, 2)); + emit NodeApproved(_enodeId, _ip, _port, _raftport, _orgId); + } + + /** @notice called at the time of new org creation to add node to org + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _orgId org id to which the enode belongs + */ + function addNode(string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, string memory _orgId) public + onlyImplementation + enodeDoesNotExists(_enodeId) { + numberOfNodes++; + enodeIdToIndex[keccak256(abi.encode(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _ip, _port, _raftport, _orgId, 1)); + emit NodeProposed(_enodeId, _ip, _port, _raftport, _orgId); + } + + /** @notice called org admins to add new enodes to the org or sub orgs + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _orgId org or sub org id to which the enode belongs + */ + function addOrgNode(string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, string memory _orgId) public + onlyImplementation + enodeDoesNotExists(_enodeId) { + numberOfNodes++; + enodeIdToIndex[keccak256(abi.encode(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _ip, _port, _raftport, _orgId, 2)); + emit NodeApproved(_enodeId, _ip, _port, _raftport, _orgId); + } + + /** @notice function to approve the node addition. only called at the time + master org creation by network admin + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _orgId org or sub org id to which the enode belongs + */ + function approveNode(string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, string memory _orgId) public + onlyImplementation + enodeExists(_enodeId) { + // node should belong to the passed org + require(_checkOrg(_enodeId, _orgId), "enode id does not belong to the passed org id"); + require(_getNodeStatus(_enodeId) == 1, "nothing pending for approval"); + uint256 nodeIndex = _getNodeIndex(_enodeId); + if (keccak256(abi.encode(nodeList[nodeIndex].ip)) != keccak256(abi.encode(_ip)) || nodeList[nodeIndex].port != _port || nodeList[nodeIndex].raftPort != _raftport) { + return; + } + nodeList[nodeIndex].status = 2; + emit NodeApproved(nodeList[nodeIndex].enodeId, _ip, _port, _raftport, nodeList[nodeIndex].orgId); + } + + /** @notice updates the node status. can be called for deactivating/ + blacklisting and reactivating a deactivated node + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _orgId org or sub org id to which the enode belong + * @param _action action being performed + * @dev action can have any of the following values + 1 - Suspend the node + 2 - Revoke suspension of a suspended node + 3 - blacklist a node + 4 - initiate the recovery of a blacklisted node + 5 - blacklisted node recovery fully approved. mark to active + */ + function updateNodeStatus(string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, string memory _orgId, uint256 _action) public + onlyImplementation + enodeExists(_enodeId) { + // node should belong to the org + require(_checkOrg(_enodeId, _orgId), "enode id does not belong to the passed org"); + require((_action == 1 || _action == 2 || _action == 3 || _action == 4 || _action == 5), + "invalid operation. wrong action passed"); + + uint256 nodeIndex = _getNodeIndex(_enodeId); + if (keccak256(abi.encode(nodeList[nodeIndex].ip)) != keccak256(abi.encode(_ip)) || nodeList[nodeIndex].port != _port || nodeList[nodeIndex].raftPort != _raftport) { + return; + } + + if (_action == 1) { + require(_getNodeStatus(_enodeId) == 2, "operation cannot be performed"); + nodeList[nodeIndex].status = 3; + emit NodeDeactivated(_enodeId, _ip, _port, _raftport, _orgId); + } + else if (_action == 2) { + require(_getNodeStatus(_enodeId) == 3, "operation cannot be performed"); + nodeList[nodeIndex].status = 2; + emit NodeActivated(_enodeId, _ip, _port, _raftport, _orgId); + } + else if (_action == 3) { + nodeList[nodeIndex].status = 4; + emit NodeBlacklisted(_enodeId, _ip, _port, _raftport, _orgId); + } else if (_action == 4) { + // node should be in blacklisted state + require(_getNodeStatus(_enodeId) == 4, "operation cannot be performed"); + nodeList[nodeIndex].status = 5; + emit NodeRecoveryInitiated(_enodeId, _ip, _port, _raftport, _orgId); + } else { + // node should be in initiated recovery state + require(_getNodeStatus(_enodeId) == 5, "operation cannot be performed"); + nodeList[nodeIndex].status = 2; + emit NodeRecoveryCompleted(_enodeId, _ip, _port, _raftport, _orgId); + } + } + + // private functions + /** @notice returns the node index for given enode id + * @param _enodeId enode id + * @return trur or false + */ + function _getNodeIndex(string memory _enodeId) internal view + returns (uint256) { + return enodeIdToIndex[keccak256(abi.encode(_enodeId))] - 1; + } + + /** @notice checks if enode id is linked to the org id passed + * @param _enodeId enode id + * @param _orgId org or sub org id to which the enode belongs + * @return true or false + */ + function _checkOrg(string memory _enodeId, string memory _orgId) internal view + returns (bool) { + return (keccak256(abi.encode(nodeList[_getNodeIndex(_enodeId)].orgId)) == keccak256(abi.encode(_orgId))); + } + + /** @notice returns the node status for a given enode id + * @param _enodeId enode id + * @return node status + */ + function _getNodeStatus(string memory _enodeId) internal view returns (uint256) { + if (enodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0) { + return 0; + } + return nodeList[_getNodeIndex(_enodeId)].status; + } + + /** @notice checks if the node is allowed to connect or not + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @return bool indicating if the node is allowed to connect or not + */ + function connectionAllowed(string memory _enodeId, string memory _ip, uint16 _port) public view onlyImplementation + returns (bool){ + if (enodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0) { + return false; + } + uint256 nodeIndex = _getNodeIndex(_enodeId); + if (nodeList[nodeIndex].status == 2 && keccak256(abi.encode(nodeList[nodeIndex].ip)) == keccak256(abi.encode(_ip))) { + return true; + } + + return false; + } +} diff --git a/permission/v2/contract/OrgManager.sol b/permission/v2/contract/OrgManager.sol new file mode 100644 index 0000000000..00d012b213 --- /dev/null +++ b/permission/v2/contract/OrgManager.sol @@ -0,0 +1,390 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; +/** @title Organization manager contract + * @notice This contract holds implementation logic for all org management + functionality. This can be called only by the implementation + contract only. there are few view functions exposed as public and + can be called directly. these are invoked by quorum for populating + permissions data in cache + * @dev the status of the organization is denoted by a set of integer + values. These are as below: + 0 - Not in list, + 1 - Org proposed for approval by network admins + 2 - Org in Approved status + 3 - Org proposed for suspension and pending approval by network admins + 4 - Org in Suspended, + Once the node is blacklisted no further activity on the node is + possible. + */ +contract OrgManager { + string private adminOrgId; + PermissionsUpgradable private permUpgradable; + // checks if first time network boot up has happened or not + bool private networkBoot = false; + + // variables which control the breadth and depth of the sub org tree + uint private DEPTH_LIMIT = 4; + uint private BREADTH_LIMIT = 4; + + struct OrgDetails { + string orgId; + uint status; + string parentId; + string fullOrgId; + string ultParent; + uint pindex; + uint level; + uint [] subOrgIndexList; + } + + OrgDetails [] private orgList; + mapping(bytes32 => uint) private OrgIndex; + uint private orgNum = 0; + + // events related to Master Org add + event OrgApproved(string _orgId, string _porgId, string _ultParent, + uint _level, uint _status); + event OrgPendingApproval(string _orgId, string _porgId, string _ultParent, + uint _level, uint _status); + event OrgSuspended(string _orgId, string _porgId, string _ultParent, + uint _level); + event OrgSuspensionRevoked(string _orgId, string _porgId, string _ultParent, + uint _level); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation{ + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if the org id does not exists + * @param _orgId - org id + * @return true if org does not exist + */ + modifier orgDoesNotExist(string memory _orgId) { + require(checkOrgExists(_orgId) == false, "org exists"); + _; + } + + /** @notice checks if the org id does exists + * @param _orgId - org id + * @return true if org exists + */ + modifier orgExists(string memory _orgId) { + require(checkOrgExists(_orgId) == true, "org does not exist"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice called at the time of network initialization. sets the depth + breadth for sub orgs creation. and creates the default network + admin org as per config file + */ + function setUpOrg(string calldata _orgId, uint256 _breadth, uint256 _depth) external + onlyImplementation { + _addNewOrg("", _orgId, 1, 2); + DEPTH_LIMIT = _depth; + BREADTH_LIMIT = _breadth; + } + /** @notice function for adding a new master org to the network + * @param _orgId unique org id to be added + * @dev org will be added if it does exist + */ + function addOrg(string calldata _orgId) external + onlyImplementation + orgDoesNotExist(_orgId) { + _addNewOrg("", _orgId, 1, 1); + } + + /** @notice function for adding a new sub org under a parent org + * @param _pOrgId unique org id to be added + * @dev org will be added if it does exist + */ + function addSubOrg(string calldata _pOrgId, string calldata _orgId) external + onlyImplementation + orgDoesNotExist(string(abi.encodePacked(_pOrgId, ".", _orgId))) { + _addNewOrg(_pOrgId, _orgId, 2, 2); + } + + /** @notice updates the status of a master org. + * @param _orgId unique org id to be added + * @param _action action being performed + * @dev status cannot be updated for sub orgs. + This function can be called for the following actions: + 1 - to suspend an org + 2 - to activate the org back + */ + function updateOrg(string calldata _orgId, uint256 _action) external + onlyImplementation + orgExists(_orgId) + returns (uint256){ + require((_action == 1 || _action == 2), "invalid action. operation not allowed"); + uint256 id = _getOrgIndex(_orgId); + require(orgList[id].level == 1, "not a master org. operation not allowed"); + + uint256 reqStatus; + uint256 pendingOp; + if (_action == 1) { + reqStatus = 2; + pendingOp = 2; + } + else if (_action == 2) { + reqStatus = 4; + pendingOp = 3; + } + require(checkOrgStatus(_orgId, reqStatus) == true, + "org status does not allow the operation"); + if (_action == 1) { + _suspendOrg(_orgId); + } + else { + _revokeOrgSuspension(_orgId); + } + return pendingOp; + } + + /** @notice function to approve org status change for master orgs + * @param _orgId unique org id to be added + * @param _action approval for action + * @dev status cannot be updated for sub orgs. + This function can be called for the following actions: + 1 - to suspend an org + 2 - to activate the org back + */ + function approveOrgStatusUpdate(string calldata _orgId, uint256 _action) external + onlyImplementation + orgExists(_orgId) { + if (_action == 1) { + _approveOrgSuspension(_orgId); + } + else { + _approveOrgRevokeSuspension(_orgId); + } + } + + /** @notice function to approve org status change for master orgs + * @param _orgId unique org id to be added + */ + function approveOrg(string calldata _orgId) external + onlyImplementation { + require(checkOrgStatus(_orgId, 1) == true, "nothing to approve"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 2; + emit OrgApproved(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 2); + } + + /** @notice returns org info for a given org index + * @param _orgIndex org index + * @return org id + * @return parent org id + * @return ultimate parent id + * @return level in the org tree + * @return status + */ + function getOrgInfo(uint256 _orgIndex) external view returns (string memory, + string memory, string memory, uint256, uint256) { + return (orgList[_orgIndex].orgId, orgList[_orgIndex].parentId, + orgList[_orgIndex].ultParent, orgList[_orgIndex].level, orgList[_orgIndex].status); + } + + /** @notice returns org info for a given org id + * @param _orgId org id + * @return org id + * @return parent org id + * @return ultimate parent id + * @return level in the org tree + * @return status + */ + function getOrgDetails(string calldata _orgId) external view returns (string memory, + string memory, string memory, uint256, uint256) { + if (!checkOrgExists(_orgId)) { + return (_orgId, "", "", 0, 0); + } + uint256 _orgIndex = _getOrgIndex(_orgId); + return (orgList[_orgIndex].orgId, orgList[_orgIndex].parentId, + orgList[_orgIndex].ultParent, orgList[_orgIndex].level, orgList[_orgIndex].status); + } + + /** @notice returns the array of sub org indexes for the given org + * @param _orgId org id + * @return array of sub org indexes + */ + function getSubOrgIndexes(string calldata _orgId) external view returns (uint[] memory) { + require(checkOrgExists(_orgId) == true, "org does not exist"); + uint256 _orgIndex = _getOrgIndex(_orgId); + return (orgList[_orgIndex].subOrgIndexList); + } + /** @notice returns the master org id for the given org or sub org + * @param _orgId org id + * @return master org id + */ + function getUltimateParent(string calldata _orgId) external view + onlyImplementation + returns (string memory) { + return orgList[_getOrgIndex(_orgId)].ultParent; + } + + /** @notice returns the total number of orgs in the network + * @return master org id + */ + function getNumberOfOrgs() public view returns (uint256) { + return orgList.length; + } + + /** @notice confirms that org status is same as passed status + * @param _orgId org id + * @param _orgStatus org status + * @return true or false + */ + function checkOrgStatus(string memory _orgId, uint256 _orgStatus) + public view returns (bool){ + if (OrgIndex[keccak256(abi.encodePacked(_orgId))] == 0) { + return false; + } + uint256 id = _getOrgIndex(_orgId); + return ((OrgIndex[keccak256(abi.encodePacked(_orgId))] != 0) + && orgList[id].status == _orgStatus); + } + + /** @notice confirms that org status either active or pending suspension + * @param _orgId org id + * @return true or false + */ + function checkOrgActive(string memory _orgId) + public view returns (bool){ + if (OrgIndex[keccak256(abi.encodePacked(_orgId))] != 0) { + uint256 id = _getOrgIndex(_orgId); + if (orgList[id].status == 2 || orgList[id].status == 3) { + uint256 uid = _getOrgIndex(orgList[id].ultParent); + if (orgList[uid].status == 2 || orgList[uid].status == 3) { + return true; + } + } + } + return false; + } + + /** @notice confirms if the org exists in the network + * @param _orgId org id + * @return true or false + */ + function checkOrgExists(string memory _orgId) public view returns (bool) { + return (!(OrgIndex[keccak256(abi.encodePacked(_orgId))] == 0)); + } + + /** @notice updates the org status to suspended + * @param _orgId org id + */ + function _suspendOrg(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 2) == true, + "org not in approved status. operation cannot be done"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 3; + emit OrgPendingApproval(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 3); + } + + /** @notice revokes the suspension of an org + * @param _orgId org id + */ + function _revokeOrgSuspension(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 4) == true, "org not in suspended state"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 5; + emit OrgPendingApproval(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 5); + } + + /** @notice approval function for org suspension activity + * @param _orgId org id + */ + function _approveOrgSuspension(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 3) == true, "nothing to approve"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 4; + emit OrgSuspended(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level); + } + + /** @notice approval function for revoking org suspension + * @param _orgId org id + */ + function _approveOrgRevokeSuspension(string memory _orgId) internal { + require(checkOrgStatus(_orgId, 5) == true, "nothing to approve"); + uint256 id = _getOrgIndex(_orgId); + orgList[id].status = 2; + emit OrgSuspensionRevoked(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level); + } + + /** @notice function to add a new organization + * @param _pOrgId parent org id + * @param _orgId org id + * @param _level level in org hierarchy + * @param _status status of the org + */ + function _addNewOrg(string memory _pOrgId, string memory _orgId, + uint256 _level, uint _status) internal { + bytes32 pid = ""; + bytes32 oid = ""; + uint256 parentIndex = 0; + + if (_level == 1) {//root + oid = keccak256(abi.encodePacked(_orgId)); + } else { + pid = keccak256(abi.encodePacked(_pOrgId)); + oid = keccak256(abi.encodePacked(_pOrgId, ".", _orgId)); + } + orgNum++; + OrgIndex[oid] = orgNum; + uint256 id = orgList.length++; + if (_level == 1) { + orgList[id].level = _level; + orgList[id].pindex = 0; + orgList[id].fullOrgId = _orgId; + orgList[id].ultParent = _orgId; + } else { + parentIndex = OrgIndex[pid] - 1; + + require(orgList[parentIndex].subOrgIndexList.length < BREADTH_LIMIT, + "breadth level exceeded"); + require(orgList[parentIndex].level < DEPTH_LIMIT, + "depth level exceeded"); + + orgList[id].level = orgList[parentIndex].level + 1; + orgList[id].pindex = parentIndex; + orgList[id].ultParent = orgList[parentIndex].ultParent; + uint256 subOrgId = orgList[parentIndex].subOrgIndexList.length++; + orgList[parentIndex].subOrgIndexList[subOrgId] = id; + orgList[id].fullOrgId = string(abi.encodePacked(_pOrgId, ".", _orgId)); + } + orgList[id].orgId = _orgId; + orgList[id].parentId = _pOrgId; + orgList[id].status = _status; + if (_status == 1) { + emit OrgPendingApproval(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 1); + } + else { + emit OrgApproved(orgList[id].orgId, orgList[id].parentId, + orgList[id].ultParent, orgList[id].level, 2); + } + } + + /** @notice returns the org index from the org list for the given org + * @return org index + */ + function _getOrgIndex(string memory _orgId) private view returns (uint){ + return OrgIndex[keccak256(abi.encodePacked(_orgId))] - 1; + } + +} diff --git a/permission/v2/contract/PermissionsImplementation.sol b/permission/v2/contract/PermissionsImplementation.sol new file mode 100644 index 0000000000..44cf21a09f --- /dev/null +++ b/permission/v2/contract/PermissionsImplementation.sol @@ -0,0 +1,760 @@ +pragma solidity ^0.5.3; + +import "./RoleManager.sol"; +import "./AccountManager.sol"; +import "./VoterManager.sol"; +import "./NodeManager.sol"; +import "./OrgManager.sol"; +import "./PermissionsUpgradable.sol"; + +/** @title Permissions Implementation Contract + * @notice This contract holds implementation logic for all permissions + related functionality. This can be called only by the interface + contract. + */ +contract PermissionsImplementation { + AccountManager private accountManager; + RoleManager private roleManager; + VoterManager private voterManager; + NodeManager private nodeManager; + OrgManager private orgManager; + PermissionsUpgradable private permUpgradable; + + string private adminOrg; + string private adminRole; + string private orgAdminRole; + + + uint256 private fullAccess = 3; + + /** @dev this variable is meant for tracking the initial network boot up + once the network boot up is done the value is set to true + */ + bool private networkBoot = false; + + event PermissionsInitialized(bool _networkBootStatus); + + + /** @notice modifier to confirm that caller is the interface contract + */ + modifier onlyInterface{ + require(msg.sender == permUpgradable.getPermInterface(), + "can be called by interface contract only"); + _; + } + /** @notice modifier to confirm that caller is the upgradable contract + */ + modifier onlyUpgradeable { + require(msg.sender == address(permUpgradable), "invalid caller"); + _; + } + + /** @notice confirms if the network boot status is equal to passed value + * @param _status true/false + */ + modifier networkBootStatus(bool _status){ + require(networkBoot == _status, "Incorrect network boot status"); + _; + } + + /** @notice confirms that the account passed is network admin account + * @param _account account id + */ + modifier networkAdmin(address _account) { + require(isNetworkAdmin(_account) == true, "account is not a network admin account"); + _; + } + + /** @notice confirms that the account passed is org admin account + * @param _account account id + * @param _orgId org id to which the account belongs + */ + modifier orgAdmin(address _account, string memory _orgId) { + require(isOrgAdmin(_account, _orgId) == true, "account is not a org admin account"); + _; + } + + /** @notice confirms that org does not exist + * @param _orgId org id + */ + modifier orgNotExists(string memory _orgId) { + require(_checkOrgExists(_orgId) != true, "org exists"); + _; + } + + /** @notice confirms that org exists + * @param _orgId org id + */ + modifier orgExists(string memory _orgId) { + require(_checkOrgExists(_orgId) == true, "org does not exist"); + _; + } + + /** @notice checks of the passed org id is in approved status + * @param _orgId org id + */ + modifier orgApproved(string memory _orgId) { + require(checkOrgApproved(_orgId) == true, "org not in approved status"); + _; + } + + /** @notice constructor accepts the contracts addresses of other deployed + contracts of the permissions model + * @param _permUpgradable - address of permissions upgradable contract + * @param _orgManager - address of org manager contract + * @param _rolesManager - address of role manager contract + * @param _accountManager - address of account manager contract + * @param _voterManager - address of voter manager contract + * @param _nodeManager - address of node manager contract + */ + constructor (address _permUpgradable, address _orgManager, address _rolesManager, + address _accountManager, address _voterManager, address _nodeManager) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + orgManager = OrgManager(_orgManager); + roleManager = RoleManager(_rolesManager); + accountManager = AccountManager(_accountManager); + voterManager = VoterManager(_voterManager); + nodeManager = NodeManager(_nodeManager); + } + + // initial set up related functions + /** @notice for permissions its necessary to define the initial admin org + id, network admin role id and default org admin role id. this + sets these values at the time of network boot up + * @param _nwAdminOrg - address of permissions upgradable contract + * @param _nwAdminRole - address of org manager contract + * @param _oAdminRole - address of role manager contract + * @dev this function will be executed only once as part of the boot up + */ + function setPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, + string calldata _oAdminRole) external onlyInterface + networkBootStatus(false) { + adminOrg = _nwAdminOrg; + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + } + + /** @notice when migrating implementation contract, the values of these + key values need to be set from the previous implementation + contract. this function allows these values to be set + * @param _nwAdminOrg - address of permissions upgradable contract + * @param _nwAdminRole - address of org manager contract + * @param _oAdminRole - address of role manager contract + * @param _networkBootStatus - network boot status true/false + */ + function setMigrationPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, + string calldata _oAdminRole, bool _networkBootStatus) external onlyUpgradeable + networkBootStatus(false) { + adminOrg = _nwAdminOrg; + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + networkBoot = _networkBootStatus; + } + + /** @notice called at the time of network initialization. sets up + network admin org with allowed sub org depth and breadth + creates the network admin for the network admin org + sets the default values required by account manager contract + * @param _breadth - number of sub orgs allowed at parent level + * @param _depth - levels of sub org nesting allowed at parent level + */ + function init(uint256 _breadth, uint256 _depth) external + onlyInterface + networkBootStatus(false) { + orgManager.setUpOrg(adminOrg, _breadth, _depth); + roleManager.addRole(adminRole, adminOrg, fullAccess, true, true); + accountManager.setDefaults(adminRole, orgAdminRole); + } + /** @notice as a part of network initialization add all nodes which + are part of static-nodes.json as nodes belonging to + network admin org + * @param _enodeId - enode id + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + */ + function addAdminNode(string calldata _enodeId, string calldata _ip, uint16 _port, uint16 _raftport) external + onlyInterface + networkBootStatus(false) { + nodeManager.addAdminNode(_enodeId, _ip, _port, _raftport, adminOrg); + } + + /** @notice as a part of network initialization add all accounts which are + passed via permission-config.json as network administrator + accounts + * @param _account - account id + */ + function addAdminAccount(address _account) external + onlyInterface + networkBootStatus(false) { + updateVoterList(adminOrg, _account, true); + accountManager.assignAdminRole(_account, adminOrg, adminRole, 2); + } + + /** @notice once the network initialization is complete, sets the network + boot status to true + * @return network boot status + * @dev this will be called only once from geth as a part of + * @dev network initialization + */ + function updateNetworkBootStatus() external + onlyInterface + networkBootStatus(false) + returns (bool){ + networkBoot = true; + emit PermissionsInitialized(networkBoot); + return networkBoot; + } + + /** @notice function to add a new organization to the network. creates org + record and marks it as pending approval. adds the passed node + node manager contract. adds the account with org admin role to + account manager contracts. creates voting record for approval + by other network admin accounts + * @param _orgId unique organization id + * @param _enodeId enode id linked to the organization + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _account account id. this will have the org admin privileges + */ + function addOrg(string memory _orgId, string memory _enodeId, + string memory _ip, uint16 _port, uint16 _raftport, address _account, address _caller) public + onlyInterface + { + require(networkBoot == true, "Incorrect network boot status"); + require(isNetworkAdmin(_caller) == true, "account is not a network admin account"); + voterManager.addVotingItem(adminOrg, _orgId, _enodeId, _account, 1); + orgManager.addOrg(_orgId); + nodeManager.addNode(_enodeId, _ip, _port, _raftport, _orgId); + require(validateAccount(_account, _orgId) == true, + "Operation cannot be performed"); + accountManager.assignAdminRole(_account, _orgId, orgAdminRole, 1); + } + + /** @notice functions to approve a pending approval org record by networ + admin account. once majority votes are received the org is + marked as approved + * @param _orgId unique organization id + * @param _enodeId enode id linked to the organization + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _account account id this will have the org admin privileges + */ + function approveOrg(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + address _account, address _caller) public + onlyInterface + { + require(isNetworkAdmin(_caller) == true, "account is not a network admin account"); + require(_checkOrgStatus(_orgId, 1) == true, "Nothing to approve"); + if ((processVote(adminOrg, _caller, 1))) { + orgManager.approveOrg(_orgId); + roleManager.addRole(orgAdminRole, _orgId, fullAccess, true, true); + nodeManager.approveNode(_enodeId, _ip, _port, _raftport, _orgId); + accountManager.addNewAdmin(_orgId, _account); + } + } + + /** @notice function to create a sub org under a given parent org. + * @param _pOrgId parent org id under which the sub org is being added + * @param _orgId unique id for the sub organization + * @param _enodeId enode id linked to the sjb organization + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @dev _enodeId is optional. parent org id should contain the complete + org hierarchy from master org id to the immediate parent. The org + hierarchy is separated by. For example, if master org ABC has a + sub organization SUB1, then while creating the sub organization at + SUB1 level, the parent org should be given as ABC.SUB1 + */ + function addSubOrg(string calldata _pOrgId, string calldata _orgId, + string calldata _enodeId, string calldata _ip, uint16 _port, uint16 _raftport, address _caller) external onlyInterface + orgExists(_pOrgId) orgAdmin(_caller, _pOrgId) { + orgManager.addSubOrg(_pOrgId, _orgId); + string memory pOrgId = string(abi.encodePacked(_pOrgId, ".", _orgId)); + if (bytes(_enodeId).length > 0) { + nodeManager.addOrgNode(_enodeId, _ip, _port, _raftport, pOrgId); + } + } + + /** @notice function to update the org status. it updates the org status + and adds a voting item for network admins to approve + * @param _orgId unique id of the organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function updateOrgStatus(string calldata _orgId, uint256 _action, address _caller) + external onlyInterface networkAdmin(_caller) { + uint256 pendingOp; + pendingOp = orgManager.updateOrg(_orgId, _action); + voterManager.addVotingItem(adminOrg, _orgId, "", address(0), pendingOp); + } + + /** @notice function to approve org status change. the org status is + changed once the majority votes are received from network + admin accounts. + * @param _orgId unique id for the sub organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function approveOrgStatus(string calldata _orgId, uint256 _action, address _caller) + external onlyInterface networkAdmin(_caller) { + require((_action == 1 || _action == 2), "Operation not allowed"); + uint256 pendingOp; + uint256 orgStatus; + if (_action == 1) { + pendingOp = 2; + orgStatus = 3; + } + else if (_action == 2) { + pendingOp = 3; + orgStatus = 5; + } + require(_checkOrgStatus(_orgId, orgStatus) == true, "operation not allowed"); + if ((processVote(adminOrg, _caller, pendingOp))) { + orgManager.approveOrgStatusUpdate(_orgId, _action); + } + } + + // Role related functions + + /** @notice function to add new role definition to an organization + can be executed by the org admin account only + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + * @param _access account access type allowed for the role + * @param _voter bool indicates if the role is voter role or not + * @param _admin bool indicates if the role is an admin role + * @dev account access type can have of the following four values: + 0 - Read only + 1 - value transfer + 2 - contract deploy + 3 - full access + 4 - contract call + 5 - value transfer and contract call + 6 - value transfer and contract deploy + 7 - contract call and deploy + */ + function addNewRole(string calldata _roleId, string calldata _orgId, + uint256 _access, bool _voter, bool _admin, address _caller) external + onlyInterface orgApproved(_orgId) orgAdmin(_caller, _orgId) { + //add new roles can be created by org admins only + roleManager.addRole(_roleId, _orgId, _access, _voter, _admin); + } + + /** @notice function to remove a role definition from an organization + can be executed by the org admin account only + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + */ + function removeRole(string calldata _roleId, string calldata _orgId, + address _caller) external onlyInterface orgApproved(_orgId) + orgAdmin(_caller, _orgId) { + require(((keccak256(abi.encode(_roleId)) != keccak256(abi.encode(adminRole))) && + (keccak256(abi.encode(_roleId)) != keccak256(abi.encode(orgAdminRole)))), + "admin roles cannot be removed"); + roleManager.removeRole(_roleId, _orgId); + } + + // Account related functions + /** @notice function to assign network admin/org admin role to an account + this can be executed by network admin accounts only. it assigns + the role to the accounts and creates voting record for network + admin accounts + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _roleId role id to be assigned to the account + */ + function assignAdminRole(string calldata _orgId, address _account, + string calldata _roleId, address _caller) external + onlyInterface orgExists(_orgId) networkAdmin(_caller) { + accountManager.assignAdminRole(_account, _orgId, _roleId, 1); + //add voting item + voterManager.addVotingItem(adminOrg, _orgId, "", _account, 4); + } + + /** @notice function to approve network admin/org admin role assigment + this can be executed by network admin accounts only. + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + */ + function approveAdminRole(string calldata _orgId, address _account, + address _caller) external onlyInterface networkAdmin(_caller) { + if ((processVote(adminOrg, _caller, 4))) { + (bool ret, address account) = accountManager.removeExistingAdmin(_orgId); + if (ret) { + updateVoterList(adminOrg, account, false); + } + bool ret1 = accountManager.addNewAdmin(_orgId, _account); + if (ret1) { + updateVoterList(adminOrg, _account, true); + } + } + } + + /** @notice function to update account status. can be executed by org admin + account only. + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _action 1-suspend 2-activate back 3-blacklist + */ + function updateAccountStatus(string calldata _orgId, address _account, + uint256 _action, address _caller) external onlyInterface + orgAdmin(_caller, _orgId) { + // ensure that the action passed to this call is proper and is not + // called with action 4 and 5 which are actions for blacklisted account + // recovery + require((_action == 1 || _action == 2 || _action == 3), + "invalid action. operation not allowed"); + accountManager.updateAccountStatus(_orgId, _account, _action); + } + + // Node related functions + + /** @notice function to add a new node to the organization. can be invoked + org admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being dded to the org + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + + */ + function addNode(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, address _caller) + public + onlyInterface + orgApproved(_orgId) + { + // check that the node is not part of another org + require(isOrgAdmin(_caller, _orgId) == true, "account is not a org admin account"); + nodeManager.addOrgNode(_enodeId, _ip, _port, _raftport, _orgId); + } + + /** @notice function to update node status. can be invoked by org admin + account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being dded to the org + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _action 1-deactivate, 2-activate back, 3-blacklist the node + */ + function updateNodeStatus(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + uint256 _action, address _caller) public + onlyInterface + { + require(isOrgAdmin(_caller, _orgId) == true, "account is not a org admin account"); + // ensure that the action passed to this call is proper and is not + // called with action 4 and 5 which are actions for blacklisted node + // recovery + require((_action == 1 || _action == 2 || _action == 3), + "invalid action. operation not allowed"); + nodeManager.updateNodeStatus(_enodeId, _ip, _port, _raftport, _orgId, _action); + } + + /** @notice function to initiate blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being dded to the org + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function startBlacklistedNodeRecovery(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + address _caller) public + onlyInterface + networkAdmin(_caller) + { + // update the node status as recovery initiated. action for this is 4 + nodeManager.updateNodeStatus(_enodeId, _ip, _port, _raftport, _orgId, 4); + + // add a voting record with pending op of 5 which corresponds to blacklisted node + // recovery + voterManager.addVotingItem(adminOrg, _orgId, _enodeId, address(0), 5); + } + + /** @notice function to initiate blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being dded to the org + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function approveBlacklistedNodeRecovery(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + address _caller) public + onlyInterface + networkAdmin(_caller) + { + // check if majority votes are received. pending op type is passed as 5 + // which stands for black listed node recovery + if ((processVote(adminOrg, _caller, 5))) { + // update the node back to active + nodeManager.updateNodeStatus(_enodeId, _ip, _port, _raftport, _orgId, 5); + } + } + + /** @notice function to initaite blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being dded to the org + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function startBlacklistedAccountRecovery(string calldata _orgId, address _account, + address _caller) external + onlyInterface + networkAdmin(_caller) + { + // update the account status as recovery initiated. action for this is 4 + accountManager.updateAccountStatus(_orgId, _account, 4); + // add a voting record with pending op of 5 which corresponds to blacklisted node + // recovery + voterManager.addVotingItem(adminOrg, _orgId, "", _account, 6); + } + + /** @notice function to initaite blacklisted nodes recovery. this can be + invoked by an network admin account only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being dded to the org + * @dev this function creates a voting record for other network admins to + approve the operation. The recovery is complete only after majority voting + */ + function approveBlacklistedAccountRecovery(string calldata _orgId, address _account, + address _caller) external + onlyInterface + networkAdmin(_caller) { + // check if majority votes are received. pending op type is passed as 6 + // which stands for black listed account recovery + if ((processVote(adminOrg, _caller, 6))) { + // update the node back to active + accountManager.updateAccountStatus(_orgId, _account, 5); + } + } + + /** @notice function to fetch network boot status + * @return bool network boot status + */ + function getNetworkBootStatus() external view + returns (bool){ + return networkBoot; + } + + /** @notice function to fetch detail of any pending approval activities + for network admin organization + * @param _orgId unique id of the organization to which the account belongs + */ + function getPendingOp(string calldata _orgId) external view + returns (string memory, string memory, address, uint256){ + return voterManager.getPendingOpDetails(_orgId); + } + + /** @notice function to assigns a role id to the account given account + can be executed by org admin account only + * @param _account account id + * @param _orgId organization id to which the account belongs + * @param _roleId role id to be assigned to the account + */ + function assignAccountRole(address _account, string memory _orgId, + string memory _roleId, address _caller) public + onlyInterface + orgAdmin(_caller, _orgId) + orgApproved(_orgId) { + require(validateAccount(_account, _orgId) == true, "operation cannot be performed"); + require(_roleExists(_roleId, _orgId) == true, "role does not exists"); + bool admin = roleManager.isAdminRole(_roleId, _orgId, _getUltimateParent(_orgId)); + accountManager.assignAccountRole(_account, _orgId, _roleId, admin); + } + + /** @notice function to check if passed account is an network admin account + * @param _account account id + * @return true/false + */ + function isNetworkAdmin(address _account) public view + returns (bool){ + return (keccak256(abi.encode(accountManager.getAccountRole(_account))) == keccak256(abi.encode(adminRole))); + } + + /** @notice function to check if passed account is an org admin account + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function isOrgAdmin(address _account, string memory _orgId) public view + returns (bool){ + if (accountManager.checkOrgAdmin(_account, _orgId, _getUltimateParent(_orgId))) { + return true; + } + return roleManager.isAdminRole(accountManager.getAccountRole(_account), _orgId, + _getUltimateParent(_orgId)); + } + + /** @notice function to validate the account for access change operation + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function validateAccount(address _account, string memory _orgId) public view + returns (bool){ + return (accountManager.validateAccount(_account, _orgId)); + } + + /** @notice function to update the voter list at network level. this will + be called whenever an account is assigned a network admin role + or an account having network admin role is being assigned + different role + * @param _orgId org id to which the account belongs + * @param _account account which needs to be added/removed as voter + * @param _add bool indicating if its an add or delete operation + */ + function updateVoterList(string memory _orgId, address _account, bool _add) internal { + if (_add) { + voterManager.addVoter(_orgId, _account); + } + else { + voterManager.deleteVoter(_orgId, _account); + } + } + + /** @notice whenever a network admin account votes on a pending item, this + function processes the vote. + * @param _orgId org id of the caller + * @param _caller account which approving the operation + * @param _pendingOp operation for which the approval is being done + * @dev the list of pending ops are managed in voter manager contract + */ + function processVote(string memory _orgId, address _caller, uint256 _pendingOp) internal + returns (bool){ + return voterManager.processVote(_orgId, _caller, _pendingOp); + } + + /** @notice returns various permissions policy related parameters + * @return adminOrg admin org id + * @return adminRole default network admin role + * @return orgAdminRole default org admin role + * @return networkBoot network boot status + */ + function getPolicyDetails() external view + returns (string memory, string memory, string memory, bool){ + return (adminOrg, adminRole, orgAdminRole, networkBoot); + } + + /** @notice checks if the passed org exists or not + * @param _orgId org id + * @return true/false + */ + function _checkOrgExists(string memory _orgId) internal view + returns (bool){ + return orgManager.checkOrgExists(_orgId); + } + + /** @notice checks if the passed org is in approved status + * @param _orgId org id + * @return true/false + */ + function checkOrgApproved(string memory _orgId) internal view + returns (bool){ + return orgManager.checkOrgStatus(_orgId, 2); + } + + /** @notice checks if the passed org is in the status passed + * @param _orgId org id + * @param _status status to be checked for + * @return true/false + */ + function _checkOrgStatus(string memory _orgId, uint256 _status) internal view + returns (bool){ + return orgManager.checkOrgStatus(_orgId, _status); + } + + /** @notice checks if org admin account exists for the passed org id + * @param _orgId org id + * @return true/false + */ + function _checkOrgAdminExists(string memory _orgId) internal view + returns (bool){ + return accountManager.orgAdminExists(_orgId); + } + + /** @notice checks if role id exists for the passed org_id + * @param _roleId role id + * @param _orgId org id + * @return true/false + */ + function _roleExists(string memory _roleId, string memory _orgId) internal view + returns (bool){ + return roleManager.roleExists(_roleId, _orgId, _getUltimateParent(_orgId)); + } + + /** @notice checks if the role id for the org is a voter role + * @param _roleId role id + * @param _orgId org id + * @return true/false + */ + function _isVoterRole(string memory _roleId, string memory _orgId) internal view + returns (bool){ + return roleManager.isVoterRole(_roleId, _orgId, _getUltimateParent(_orgId)); + } + + /** @notice returns the ultimate parent for a given org id + * @param _orgId org id + * @return ultimate parent org id + */ + function _getUltimateParent(string memory _orgId) internal view + returns (string memory){ + return orgManager.getUltimateParent(_orgId); + } + + /** @notice checks if the node is allowed to connect or not + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @return bool indicating if the node is allowed to connect or not + */ + function connectionAllowed(string calldata _enodeId, string calldata _ip, uint16 _port) external view returns (bool) { + if (!networkBoot){ + return true; + } + return nodeManager.connectionAllowed(_enodeId, _ip, _port); + } + + /** @notice checks if the account is allowed to transact or not + * @param _sender source account + * @param _target target account + * @param _value value being transferred + * @param _gasPrice gas price + * @param _gasLimit gas limit + * @param _payload payload for transactions on contracts + * @return bool indicating if the account is allowed to transact or not + */ + function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes calldata _payload) + external view returns (bool) { + if (!networkBoot){ + return true; + } + + if (accountManager.getAccountStatus(_sender) == 2) { + (string memory act_org, string memory act_role) = accountManager.getAccountOrgRole(_sender); + string memory act_uOrg = _getUltimateParent(act_org); + if (orgManager.checkOrgActive(act_org)) { + if (isNetworkAdmin(_sender) || isOrgAdmin(_sender, act_org)) { + return true; + } + + uint256 typeOfxn = 1; + if (_target == address(0)) { + typeOfxn = 2; + } + else if (_payload.length > 0) { + typeOfxn = 3; + } + return roleManager.transactionAllowed(act_role, act_org, act_uOrg, typeOfxn); + } + } + return false; + } +} diff --git a/permission/v2/contract/PermissionsInterface.sol b/permission/v2/contract/PermissionsInterface.sol new file mode 100644 index 0000000000..60406761dc --- /dev/null +++ b/permission/v2/contract/PermissionsInterface.sol @@ -0,0 +1,351 @@ +pragma solidity ^0.5.3; + +import "./PermissionsImplementation.sol"; +import "./PermissionsUpgradable.sol"; + +/** @title Permissions Interface Contract + * @notice This contract is the interface for permissions implementation + contract. for any call, it forwards the call to the implementation + contract + */ +contract PermissionsInterface { + PermissionsImplementation private permImplementation; + PermissionsUpgradable private permUpgradable; + address private permImplUpgradeable; + + /** @notice constructor + * @param _permImplUpgradeable permissions upgradable contract address + */ + constructor(address _permImplUpgradeable) public { + permImplUpgradeable = _permImplUpgradeable; + } + + /** @notice confirms that the caller is the address of upgradable + contract + */ + modifier onlyUpgradeable { + require(msg.sender == permImplUpgradeable, "invalid caller"); + _; + } + + /** @notice interface for setting the permissions policy in implementation + * @param _nwAdminOrg network admin organization id + * @param _nwAdminRole default network admin role id + * @param _oAdminRole default organization admin role id + */ + function setPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, + string calldata _oAdminRole) external { + permImplementation.setPolicy(_nwAdminOrg, _nwAdminRole, _oAdminRole); + } + + /** @notice interface to initializes the breadth and depth values for + sub organization management + * @param _breadth controls the number of sub org a parent org can have + * @param _depth controls the depth of nesting allowed for sub orgs + */ + function init(uint256 _breadth, uint256 _depth) external { + permImplementation.init(_breadth, _depth); + } + + /** @notice interface to add new node to an admin organization + * @param _enodeId enode id of the node to be added + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + */ + function addAdminNode(string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport) public { + permImplementation.addAdminNode(_enodeId, _ip, _port, _raftport); + } + + /** @notice interface to add accounts to an admin organization + * @param _acct account address to be added + */ + function addAdminAccount(address _acct) external { + permImplementation.addAdminAccount(_acct); + } + + /** @notice interface to update network boot up status + * @return bool true or false + */ + function updateNetworkBootStatus() external + returns (bool) + { + return permImplementation.updateNetworkBootStatus(); + } + + /** @notice interface to fetch network boot status + * @return bool network boot status + */ + function getNetworkBootStatus() external view returns (bool){ + return permImplementation.getNetworkBootStatus(); + } + + /** @notice interface to add a new organization to the network + * @param _orgId unique organization id + * @param _enodeId enode id linked to the organization + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _account account id. this will have the org admin privileges + */ + function addOrg(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + address _account) public { + permImplementation.addOrg(_orgId, _enodeId, _ip, _port, _raftport, _account, msg.sender); + } + + /** @notice interface to approve a newly added organization + * @param _orgId unique organization id + * @param _enodeId enode id linked to the organization + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _account account id this will have the org admin privileges + */ + function approveOrg(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + address _account) public { + permImplementation.approveOrg(_orgId, _enodeId, _ip, _port, _raftport, _account, msg.sender); + } + + /** @notice interface to add sub org under an org + * @param _pOrgId parent org id under which the sub org is being added + * @param _orgId unique id for the sub organization + * @param _enodeId enode id linked to the sjb organization + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + */ + function addSubOrg(string memory _pOrgId, string memory _orgId, + string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport) public { + permImplementation.addSubOrg(_pOrgId, _orgId, _enodeId, _ip, _port, _raftport, msg.sender); + } + + /** @notice interface to update the org status + * @param _orgId unique id of the organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function updateOrgStatus(string calldata _orgId, uint256 _action) external { + permImplementation.updateOrgStatus(_orgId, _action, msg.sender); + } + + /** @notice interface to approve org status change + * @param _orgId unique id for the sub organization + * @param _action 1 for suspending an org and 2 for revoke of suspension + */ + function approveOrgStatus(string calldata _orgId, uint256 _action) external { + permImplementation.approveOrgStatus(_orgId, _action, msg.sender); + } + + /** @notice interface to add a new role definition to an organization + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + * @param _access account access type for the role + * @param _voter bool indicates if the role is voter role or not + * @param _admin bool indicates if the role is an admin role + * @dev account access type can have of the following four values: + 0 - Read only + 1 - Transact access + 2 - Contract deployment access. Can transact as well + 3 - Full access + */ + function addNewRole(string calldata _roleId, string calldata _orgId, + uint256 _access, bool _voter, bool _admin) external { + permImplementation.addNewRole(_roleId, _orgId, _access, _voter, _admin, msg.sender); + } + + /** @notice interface to remove a role definition from an organization + * @param _roleId unique id for the role + * @param _orgId unique id of the organization to which the role belongs + */ + function removeRole(string calldata _roleId, string calldata _orgId) external { + permImplementation.removeRole(_roleId, _orgId, msg.sender); + } + + /** @notice interface to assign network admin/org admin role to an account + this can be executed by network admin accounts only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _roleId role id to be assigned to the account + */ + function assignAdminRole(string calldata _orgId, address _account, + string calldata _roleId) external { + permImplementation.assignAdminRole(_orgId, _account, _roleId, msg.sender); + + } + /** @notice interface to approve network admin/org admin role assigment + this can be executed by network admin accounts only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + */ + function approveAdminRole(string calldata _orgId, address _account) external { + permImplementation.approveAdminRole(_orgId, _account, msg.sender); + + } + + /** @notice interface to update account status + this can be executed by org admin accounts only + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id + * @param _action 1-suspending 2-activating back 3-blacklisting + */ + function updateAccountStatus(string calldata _orgId, address _account, + uint256 _action) external { + permImplementation.updateAccountStatus(_orgId, _account, _action, msg.sender); + } + + /** @notice interface to add a new node to the organization + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being dded to the org + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + */ + function addNode(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport) public { + permImplementation.addNode(_orgId, _enodeId, _ip, _port, _raftport, msg.sender); + + } + + /** @notice interface to update node status + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being dded to the org + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + * @param _action 1-deactivate, 2-activate back, 3-blacklist the node + */ + function updateNodeStatus(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport, + uint256 _action) public { + permImplementation.updateNodeStatus(_orgId, _enodeId, _ip, _port, _raftport, _action, msg.sender); + } + + /** @notice interface to initiate blacklisted node recovery + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being recovered + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + */ + function startBlacklistedNodeRecovery(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport) + public { + permImplementation.startBlacklistedNodeRecovery(_orgId, _enodeId, _ip, _port, _raftport, msg.sender); + } + + /** @notice interface to approve blacklisted node recoevry + * @param _orgId unique id of the organization to which the account belongs + * @param _enodeId enode id being recovered + * @param _ip IP of node + * @param _port tcp port of node + * @param _raftport raft port of node + */ + function approveBlacklistedNodeRecovery(string memory _orgId, string memory _enodeId, string memory _ip, uint16 _port, uint16 _raftport) + public { + permImplementation.approveBlacklistedNodeRecovery(_orgId, _enodeId, _ip, _port, _raftport, msg.sender); + } + + /** @notice interface to initiate blacklisted account recovery + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being recovered + */ + function startBlacklistedAccountRecovery(string calldata _orgId, address _account) + external { + permImplementation.startBlacklistedAccountRecovery(_orgId, _account, msg.sender); + } + + /** @notice interface to approve blacklisted node recovery + * @param _orgId unique id of the organization to which the account belongs + * @param _account account id being recovered + */ + function approveBlacklistedAccountRecovery(string calldata _orgId, address _account) + external { + permImplementation.approveBlacklistedAccountRecovery(_orgId, _account, msg.sender); + } + + /** @notice interface to fetch detail of any pending approval activities + for network admin organization + * @param _orgId unique id of the organization to which the account belongs + */ + function getPendingOp(string calldata _orgId) external view + returns (string memory, string memory, address, uint256) { + return permImplementation.getPendingOp(_orgId); + } + + /** @notice sets the permissions implementation contract address + can be called from upgradable contract only + * @param _permImplementation permissions implementation contract address + */ + function setPermImplementation(address _permImplementation) external + onlyUpgradeable { + permImplementation = PermissionsImplementation(_permImplementation); + } + + /** @notice returns the address of permissions implementation contract + * @return permissions implementation contract address + */ + function getPermissionsImpl() external view returns (address) { + return address(permImplementation); + } + + /** @notice interface to assigns a role id to the account give + * @param _account account id + * @param _orgId organization id to which the account belongs + * @param _roleId role id to be assigned to the account + */ + function assignAccountRole(address _account, string calldata _orgId, + string calldata _roleId) external { + permImplementation.assignAccountRole(_account, _orgId, _roleId, msg.sender); + + } + + /** @notice interface to check if passed account is an network admin account + * @param _account account id + * @return true/false + */ + function isNetworkAdmin(address _account) external view returns (bool) { + return permImplementation.isNetworkAdmin(_account); + } + + /** @notice interface to check if passed account is an org admin account + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function isOrgAdmin(address _account, string calldata _orgId) + external view returns (bool) { + return permImplementation.isOrgAdmin(_account, _orgId); + } + + /** @notice interface to validate the account for access change operation + * @param _account account id + * @param _orgId organization id + * @return true/false + */ + function validateAccount(address _account, string calldata _orgId) + external view returns (bool) { + return permImplementation.validateAccount(_account, _orgId); + } + + /** @notice checks if the node is allowed to connect or not + * @param _enodeId enode id + * @param _ip IP of node + * @param _port tcp port of node + * @return bool indicating if the node is allowed to connect or not + */ + function connectionAllowed(string calldata _enodeId, string calldata _ip, uint16 _port) external view returns (bool) { + return permImplementation.connectionAllowed(_enodeId, _ip, _port); + } + + + /** @notice checks if the account is allowed to transact or not + * @param _sender source account + * @param _target target account + * @param _value value being transferred + * @param _gasPrice gas price + * @param _gasLimit gas limit + * @param _payload payload for transactions on contracts + * @return bool indicating if the account is allowed to transact or not + */ + function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes calldata _payload) + external view returns (bool) { + return permImplementation.transactionAllowed(_sender, _target, _value, _gasPrice, _gasLimit, _payload); + } + +} diff --git a/permission/v2/contract/PermissionsUpgradable.sol b/permission/v2/contract/PermissionsUpgradable.sol new file mode 100644 index 0000000000..6481666421 --- /dev/null +++ b/permission/v2/contract/PermissionsUpgradable.sol @@ -0,0 +1,103 @@ +pragma solidity ^0.5.3; + +import "./PermissionsInterface.sol"; + +/** @title Permissions Upgradable Contract + * @notice This contract holds the address of current permissions implementation + contract. The contract is owned by a guardian account. Only the + guardian account can change the implementation contract address as + business needs. + */ +contract PermissionsUpgradable { + + address private guardian; + address private permImpl; + address private permInterface; + // initDone ensures that init can be called only once + bool private initDone; + + /** @notice constructor + * @param _guardian account address + */ + constructor (address _guardian) public{ + guardian = _guardian; + initDone = false; + } + + /** @notice confirms that the caller is the guardian account + */ + modifier onlyGuardian { + require(msg.sender == guardian, "invalid caller"); + _; + } + + /** @notice executed by guardian. Links interface and implementation contract + addresses. Can be executed by guardian account only + * @param _permInterface permissions interface contract address + * @param _permImpl implementation contract address + */ + function init(address _permInterface, address _permImpl) external + onlyGuardian { + require(!initDone, "can be executed only once"); + permImpl = _permImpl; + permInterface = _permInterface; + _setImpl(permImpl); + initDone = true; + } + + /** @notice changes the implementation contract address to the new address + address passed. Can be executed by guardian account only + * @param _proposedImpl address of the new permissions implementation contract + */ + function confirmImplChange(address _proposedImpl) public + onlyGuardian { + // The policy details needs to be carried forward from existing + // implementation to new. So first these are read from existing + // implementation and then updated in new implementation + (string memory adminOrg, string memory adminRole, string memory orgAdminRole, bool bootStatus) = PermissionsImplementation(permImpl).getPolicyDetails(); + _setPolicy(_proposedImpl, adminOrg, adminRole, orgAdminRole, bootStatus); + permImpl = _proposedImpl; + _setImpl(permImpl); + } + + /** @notice function to fetch the guardian account address + * @return _guardian guardian account address + */ + function getGuardian() public view returns (address) { + return guardian; + } + + /** @notice function to fetch the current implementation address + * @return permissions implementation contract address + */ + function getPermImpl() public view returns (address) { + return permImpl; + } + /** @notice function to fetch the interface address + * @return permissions interface contract address + */ + function getPermInterface() public view returns (address) { + return permInterface; + } + + /** @notice function to set the permissions policy details in the + permissions implementation contract + * @param _permImpl permissions implementation contract address + * @param _adminOrg name of admin organization + * @param _adminRole name of the admin role + * @param _orgAdminRole name of default organization admin role + * @param _bootStatus network boot status + */ + function _setPolicy(address _permImpl, string memory _adminOrg, string memory _adminRole, string memory _orgAdminRole, bool _bootStatus) private { + PermissionsImplementation(_permImpl).setMigrationPolicy(_adminOrg, _adminRole, _orgAdminRole, _bootStatus); + } + + /** @notice function to set the permissions implementation contract address + in the permissions interface contract + * @param _permImpl permissions implementation contract address + */ + function _setImpl(address _permImpl) private { + PermissionsInterface(permInterface).setPermImplementation(_permImpl); + } + +} diff --git a/permission/v2/contract/RoleManager.sol b/permission/v2/contract/RoleManager.sol new file mode 100644 index 0000000000..9868962d35 --- /dev/null +++ b/permission/v2/contract/RoleManager.sol @@ -0,0 +1,236 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; +/** @title Role manager contract + * @notice This contract holds implementation logic for all role management + functionality. This can be called only by the implementation + contract only. there are few view functions exposed as public and + can be called directly. these are invoked by quorum for populating + permissions data in cache + */ +contract RoleManager { + PermissionsUpgradable private permUpgradable; + + struct RoleDetails { + string roleId; + string orgId; + uint256 baseAccess; + bool isVoter; + bool isAdmin; + bool active; + } + + RoleDetails[] private roleList; + mapping(bytes32 => uint256) private roleIndex; + uint256 private numberOfRoles; + + event RoleCreated(string _roleId, string _orgId, uint256 _baseAccess, + bool _isVoter, bool _isAdmin); + event RoleRevoked(string _roleId, string _orgId); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice function to add a new role definition to an organization + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _baseAccess - can be from 0 to 7 + * @param _isVoter - bool to indicate if voter role or not + * @param _isAdmin - bool to indicate if admin role or not + * @dev base access can have any of the following values: + 0 - Read only + 1 - value transfer + 2 - contract deploy + 3 - full access + 4 - contract call + 5 - value transfer and contract call + 6 - value transfer and contract deploy + 7 - contract call and deploy + */ + function addRole(string memory _roleId, string memory _orgId, uint256 _baseAccess, + bool _isVoter, bool _isAdmin) public onlyImplementation { + require(_baseAccess < 8, "invalid access value"); + // Check if account already exists + require(roleIndex[keccak256(abi.encode(_roleId, _orgId))] == 0, "role exists for the org"); + numberOfRoles ++; + roleIndex[keccak256(abi.encode(_roleId, _orgId))] = numberOfRoles; + roleList.push(RoleDetails(_roleId, _orgId, _baseAccess, _isVoter, _isAdmin, true)); + emit RoleCreated(_roleId, _orgId, _baseAccess, _isVoter, _isAdmin); + + } + + /** @notice function to remove an existing role definition from an organization + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + */ + function removeRole(string calldata _roleId, string calldata _orgId) external + onlyImplementation { + require(roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0, "role does not exist"); + uint256 rIndex = _getRoleIndex(_roleId, _orgId); + roleList[rIndex].active = false; + emit RoleRevoked(_roleId, _orgId); + } + + /** @notice checks if the role is a voter role or not + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _ultParent - master org id + * @return true or false + * @dev checks for the role existence in the passed org and master org + */ + function isVoterRole(string calldata _roleId, string calldata _orgId, + string calldata _ultParent) external view onlyImplementation returns (bool){ + if (!(roleExists(_roleId, _orgId, _ultParent))) { + return false; + } + uint256 rIndex; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + rIndex = _getRoleIndex(_roleId, _orgId); + } + else { + rIndex = _getRoleIndex(_roleId, _ultParent); + } + return (roleList[rIndex].active && roleList[rIndex].isVoter); + } + + /** @notice checks if the role is an admin role or not + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _ultParent - master org id + * @return true or false + * @dev checks for the role existence in the passed org and master org + */ + function isAdminRole(string calldata _roleId, string calldata _orgId, + string calldata _ultParent) external view onlyImplementation returns (bool){ + if (!(roleExists(_roleId, _orgId, _ultParent))) { + return false; + } + uint256 rIndex; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + rIndex = _getRoleIndex(_roleId, _orgId); + } + else { + rIndex = _getRoleIndex(_roleId, _ultParent); + } + return (roleList[rIndex].active && roleList[rIndex].isAdmin); + } + + /** @notice returns the role details for a passed role id and org + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @return role id + * @return org id + * @return access type + * @return bool to indicate if the role is a voter role + * @return bool to indicate if the role is active + */ + function getRoleDetails(string calldata _roleId, string calldata _orgId) + external view returns (string memory roleId, string memory orgId, + uint256 accessType, bool voter, bool admin, bool active) { + if (!(roleExists(_roleId, _orgId, ""))) { + return (_roleId, "", 0, false, false, false); + } + uint256 rIndex = _getRoleIndex(_roleId, _orgId); + return (roleList[rIndex].roleId, roleList[rIndex].orgId, + roleList[rIndex].baseAccess, roleList[rIndex].isVoter, + roleList[rIndex].isAdmin, roleList[rIndex].active); + } + + /** @notice returns the role details for a passed role index + * @param _rIndex - unique identifier for the role being added + * @return role id + * @return org id + * @return access type + * @return bool to indicate if the role is a voter role + * @return bool to indicate if the role is active + */ + function getRoleDetailsFromIndex(uint256 _rIndex) external view returns + (string memory roleId, string memory orgId, uint256 accessType, + bool voter, bool admin, bool active) { + return (roleList[_rIndex].roleId, roleList[_rIndex].orgId, + roleList[_rIndex].baseAccess, roleList[_rIndex].isVoter, + roleList[_rIndex].isAdmin, roleList[_rIndex].active); + } + + /** @notice returns the total number of roles in the network + * @return total number of roles + */ + function getNumberOfRoles() external view returns (uint256) { + return roleList.length; + } + + /** @notice checks if the role exists for the given org or master org + * @param _roleId - unique identifier for the role being added + * @param _orgId - org id to which the role belongs + * @param _ultParent - master org id + * @return true or false + */ + function roleExists(string memory _roleId, string memory _orgId, + string memory _ultParent) public view returns (bool) { + uint256 id; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + id = _getRoleIndex(_roleId, _orgId); + return roleList[id].active; + } + else if (roleIndex[keccak256(abi.encode(_roleId, _ultParent))] != 0) { + id = _getRoleIndex(_roleId, _ultParent); + return roleList[id].active; + } + return false; + } + + function roleAccess(string memory _roleId, string memory _orgId, + string memory _ultParent) public view returns (uint256) { + uint256 id; + if (roleIndex[keccak256(abi.encode(_roleId, _orgId))] != 0) { + id = _getRoleIndex(_roleId, _orgId); + return roleList[id].baseAccess; + } + else if (roleIndex[keccak256(abi.encode(_roleId, _ultParent))] != 0) { + id = _getRoleIndex(_roleId, _ultParent); + return roleList[id].baseAccess; + } + return 0; + } + + function transactionAllowed(string calldata _roleId, string calldata _orgId, + string calldata _ultParent, uint256 _typeOfTxn) external view returns (bool) { + uint256 access = roleAccess(_roleId, _orgId, _ultParent); + + if (access == 3) { + return true; + } + if (_typeOfTxn == 1 && (access == 1 || access == 5 || access == 6)){ + return true; + } + if (_typeOfTxn == 2 && (access == 2 || access == 6 || access == 7)){ + return true; + } + if (_typeOfTxn == 3 && (access == 4 || access == 5 || access == 7)){ + return true; + } + + return false; + } + + /** @notice returns the role index based on role id and org id + * @param _roleId - role id + * @param _orgId - org id + * @return role index + */ + function _getRoleIndex(string memory _roleId, string memory _orgId) + internal view returns (uint256) { + return roleIndex[keccak256(abi.encode(_roleId, _orgId))] - 1; + } +} diff --git a/permission/v2/contract/VoterManager.sol b/permission/v2/contract/VoterManager.sol new file mode 100644 index 0000000000..437a8f09c5 --- /dev/null +++ b/permission/v2/contract/VoterManager.sol @@ -0,0 +1,250 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; + +/** @title Voter manager contract + * @notice This contract holds implementation logic for all account voter and + voting functionality. This can be called only by the implementation + contract only. there are few view functions exposed as public and + can be called directly. these are invoked by quorum for populating + permissions data in cache + * @dev each voting record has an attribute operation type (opType) + which denotes the activity type which is pending approval. This can + have the following values: + 0 - None - indicates no pending records for the org + 1 - New org add activity + 2 - Org suspension activity + 3 - Revoke of org suspension + 4 - Assigning admin role for a new account + 5 - Blacklisted node recovery + 6 - Blacklisted account recovery + */ +contract VoterManager { + PermissionsUpgradable private permUpgradable; + struct PendingOpDetails { + string orgId; + string enodeId; + address account; + uint256 opType; + } + + struct Voter { + address vAccount; + bool active; + } + + struct OrgVoterDetails { + string orgId; + uint256 voterCount; + uint256 validVoterCount; + uint256 voteCount; + PendingOpDetails pendingOp; + Voter [] voterList; + mapping(address => uint256) voterIndex; + mapping(uint256 => mapping(address => bool)) votingStatus; + } + + OrgVoterDetails [] private orgVoterList; + mapping(bytes32 => uint256) private VoterOrgIndex; + uint256 private orgNum = 0; + + // events related to managing voting accounts for the org + event VoterAdded(string _orgId, address _vAccount); + event VoterDeleted(string _orgId, address _vAccount); + + event VotingItemAdded(string _orgId); + event VoteProcessed(string _orgId); + + /** @notice confirms that the caller is the address of implementation + contract + */ + modifier onlyImplementation { + require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); + _; + } + + /** @notice checks if account is a valid voter record and belongs to the org + passed + * @param _orgId - org id + * @param _vAccount - voter account passed + */ + modifier voterExists(string memory _orgId, address _vAccount) { + require(_checkVoterExists(_orgId, _vAccount) == true, "must be a voter"); + _; + } + + /** @notice constructor. sets the permissions upgradable address + */ + constructor (address _permUpgradable) public { + permUpgradable = PermissionsUpgradable(_permUpgradable); + } + + /** @notice function to add a new voter account to the organization + * @param _orgId org id + * @param _vAccount - voter account + * @dev voter capability is currently enabled for network level activities + only. voting is not available for org related activities + */ + function addVoter(string calldata _orgId, address _vAccount) external + onlyImplementation { + // check if the org exists + if (VoterOrgIndex[keccak256(abi.encode(_orgId))] == 0) { + orgNum++; + VoterOrgIndex[keccak256(abi.encode(_orgId))] = orgNum; + uint256 id = orgVoterList.length++; + orgVoterList[id].orgId = _orgId; + orgVoterList[id].voterCount = 1; + orgVoterList[id].validVoterCount = 1; + orgVoterList[id].voteCount = 0; + orgVoterList[id].pendingOp.orgId = ""; + orgVoterList[id].pendingOp.enodeId = ""; + orgVoterList[id].pendingOp.account = address(0); + orgVoterList[id].pendingOp.opType = 0; + orgVoterList[id].voterIndex[_vAccount] = orgVoterList[id].voterCount; + orgVoterList[id].voterList.push(Voter(_vAccount, true)); + } + else { + uint256 id = _getVoterOrgIndex(_orgId); + // check if the voter is already present in the list + if (orgVoterList[id].voterIndex[_vAccount] == 0) { + orgVoterList[id].voterCount++; + orgVoterList[id].voterIndex[_vAccount] = orgVoterList[id].voterCount; + orgVoterList[id].voterList.push(Voter(_vAccount, true)); + orgVoterList[id].validVoterCount++; + } + else { + uint256 vid = _getVoterIndex(_orgId, _vAccount); + require(orgVoterList[id].voterList[vid].active != true, "already a voter"); + orgVoterList[id].voterList[vid].active = true; + orgVoterList[id].validVoterCount++; + } + + } + emit VoterAdded(_orgId, _vAccount); + } + + /** @notice function to delete a voter account from the organization + * @param _orgId org id + * @param _vAccount - voter account + * @dev voter capability is currently enabled for network level activities + only. voting is not available for org related activities + */ + function deleteVoter(string calldata _orgId, address _vAccount) external + onlyImplementation + voterExists(_orgId, _vAccount) { + uint256 id = _getVoterOrgIndex(_orgId); + uint256 vId = _getVoterIndex(_orgId, _vAccount); + orgVoterList[id].validVoterCount --; + orgVoterList[id].voterList[vId].active = false; + emit VoterDeleted(_orgId, _vAccount); + } + + /** @notice function to a voting item for network admin accounts to vote + * @param _authOrg org id of the authorizing org. it will be network admin org + * @param _orgId - org id for which the voting record is being created + * @param _enodeId - enode id for which the voting record is being created + * @param _account - account id for which the voting record is being created + * @param _pendingOp - operation for which voting is being done + */ + function addVotingItem(string calldata _authOrg, string calldata _orgId, + string calldata _enodeId, address _account, uint256 _pendingOp) + external onlyImplementation { + // check if anything is pending approval for the org. + // If yes another item cannot be added + require((_checkPendingOp(_authOrg, 0)), + "items pending for approval. new item cannot be added"); + uint256 id = _getVoterOrgIndex(_authOrg); + orgVoterList[id].pendingOp.orgId = _orgId; + orgVoterList[id].pendingOp.enodeId = _enodeId; + orgVoterList[id].pendingOp.account = _account; + orgVoterList[id].pendingOp.opType = _pendingOp; + // initialize vote status for voter accounts + for (uint256 i = 0; i < orgVoterList[id].voterList.length; i++) { + if (orgVoterList[id].voterList[i].active) { + orgVoterList[id].votingStatus[id][orgVoterList[id].voterList[i].vAccount] = false; + } + } + // set vote count to zero + orgVoterList[id].voteCount = 0; + emit VotingItemAdded(_authOrg); + + } + + /** @notice function processing vote of a voter account + * @param _authOrg org id of the authorizing org. it will be network admin org + * @param _vAccount - account id of the voter + * @param _pendingOp - operation which is being approved + * @return success of the voter process. either true or false + */ + function processVote(string calldata _authOrg, address _vAccount, uint256 _pendingOp) + external onlyImplementation voterExists(_authOrg, _vAccount) returns (bool) { + // check something if anything is pending approval + require(_checkPendingOp(_authOrg, _pendingOp) == true, "nothing to approve"); + uint256 id = _getVoterOrgIndex(_authOrg); + // check if vote is already processed + require(orgVoterList[id].votingStatus[id][_vAccount] != true, "cannot double vote"); + orgVoterList[id].voteCount++; + orgVoterList[id].votingStatus[id][_vAccount] = true; + emit VoteProcessed(_authOrg); + if (orgVoterList[id].voteCount > orgVoterList[id].validVoterCount / 2) { + // majority achieved, clean up pending op + orgVoterList[id].pendingOp.orgId = ""; + orgVoterList[id].pendingOp.enodeId = ""; + orgVoterList[id].pendingOp.account = address(0); + orgVoterList[id].pendingOp.opType = 0; + return true; + } + return false; + } + + /** @notice returns the details of any pending operation to be approved + * @param _orgId org id. this will be the org id of network admin org + */ + function getPendingOpDetails(string calldata _orgId) external view + onlyImplementation returns (string memory, string memory, address, uint256){ + uint256 orgIndex = _getVoterOrgIndex(_orgId); + return (orgVoterList[orgIndex].pendingOp.orgId, orgVoterList[orgIndex].pendingOp.enodeId, + orgVoterList[orgIndex].pendingOp.account, orgVoterList[orgIndex].pendingOp.opType); + } + + /** @notice checks if the voter account exists and is linked to the org + * @param _orgId org id + * @param _vAccount voter account id + * @return true or false + */ + function _checkVoterExists(string memory _orgId, address _vAccount) + internal view returns (bool){ + uint256 orgIndex = _getVoterOrgIndex(_orgId); + if (orgVoterList[orgIndex].voterIndex[_vAccount] == 0) { + return false; + } + uint256 voterIndex = _getVoterIndex(_orgId, _vAccount); + return orgVoterList[orgIndex].voterList[voterIndex].active; + } + + /** @notice checks if the pending operation exists or not + * @param _orgId org id + * @param _pendingOp type of operation + * @return true or false + */ + function _checkPendingOp(string memory _orgId, uint256 _pendingOp) + internal view returns (bool){ + return (orgVoterList[_getVoterOrgIndex(_orgId)].pendingOp.opType == _pendingOp); + } + + /** @notice returns the voter account index + */ + function _getVoterIndex(string memory _orgId, address _vAccount) + internal view returns (uint256) { + uint256 orgIndex = _getVoterOrgIndex(_orgId); + return orgVoterList[orgIndex].voterIndex[_vAccount] - 1; + } + + /** @notice returns the org index for the org from voter list + */ + function _getVoterOrgIndex(string memory _orgId) + internal view returns (uint256) { + return VoterOrgIndex[keccak256(abi.encode(_orgId))] - 1; + } + +} diff --git a/permission/v2/contract/gen/AccountManager.abi b/permission/v2/contract/gen/AccountManager.abi new file mode 100644 index 0000000000..ce8f2ba964 --- /dev/null +++ b/permission/v2/contract/gen/AccountManager.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"},{"name":"_adminRole","type":"bool"}],"name":"assignAccountRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"}],"name":"removeExistingAdmin","outputs":[{"name":"voterUpdate","type":"bool"},{"name":"account","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAccountDetails","outputs":[{"name":"","type":"address"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfAccounts","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAccountOrgRole","outputs":[{"name":"","type":"string"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"validateAccount","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAccountRole","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_action","type":"uint256"}],"name":"updateAccountStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"orgAdminExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_aIndex","type":"uint256"}],"name":"getAccountDetailsFromIndex","outputs":[{"name":"","type":"address"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"addNewAdmin","outputs":[{"name":"voterUpdate","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"}],"name":"setDefaults","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"},{"name":"_status","type":"uint256"}],"name":"assignAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"checkOrgAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"getAccountStatus","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_account","type":"address"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgAdmin","type":"bool"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"AccountAccessModified","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_account","type":"address"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgAdmin","type":"bool"}],"name":"AccountAccessRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_account","type":"address"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"AccountStatusChanged","type":"event"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/AccountManager.bin b/permission/v2/contract/gen/AccountManager.bin new file mode 100644 index 0000000000..7068e3dc7d --- /dev/null +++ b/permission/v2/contract/gen/AccountManager.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50604051602080613d2d8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055613ccb806100626000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c806384b7a84a11610097578063cef7f6af11610066578063cef7f6af1461078c578063e3483a9d1461084a578063e8b42bf414610918578063fd4fa05a14610a51576100f5565b806384b7a84a146105d7578063950145cf14610654578063b2018568146106f8578063c214e5e514610715576100f5565b8063309e36ef116100d3578063309e36ef1461038c5780636acee5fd146103a65780636b568d76146104aa57806381d66b231461053c576100f5565b8063143a5604146100fa5780631d09dc93146101cc5780632aceb5341461025d575b600080fd5b6101ca6004803603608081101561011057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561013a57600080fd5b82018360208201111561014c57600080fd5b803590602001918460018302840111600160201b8311171561016d57600080fd5b919390929091602081019035600160201b81111561018a57600080fd5b82018360208201111561019c57600080fd5b803590602001918460018302840111600160201b831117156101bd57600080fd5b9193509150351515610a77565b005b61023a600480360360208110156101e257600080fd5b810190602081018135600160201b8111156101fc57600080fd5b82018360208201111561020e57600080fd5b803590602001918460018302840111600160201b8311171561022f57600080fd5b509092509050610e75565b6040805192151583526001600160a01b0390911660208301528051918290030190f35b6102836004803603602081101561027357600080fd5b50356001600160a01b0316611401565b60405180866001600160a01b03166001600160a01b03168152602001806020018060200185815260200184151515158152602001838103835287818151815260200191508051906020019080838360005b838110156102ec5781810151838201526020016102d4565b50505050905090810190601f1680156103195780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b8381101561034c578181015183820152602001610334565b50505050905090810190601f1680156103795780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b610394611659565b60408051918252519081900360200190f35b6103cc600480360360208110156103bc57600080fd5b50356001600160a01b0316611660565b604051808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561040d5781810151838201526020016103f5565b50505050905090810190601f16801561043a5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561046d578181015183820152602001610455565b50505050905090810190601f16801561049a5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390f35b610528600480360360408110156104c057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156104ea57600080fd5b8201836020820111156104fc57600080fd5b803590602001918460018302840111600160201b8311171561051d57600080fd5b509092509050611831565b604080519115158252519081900360200190f35b6105626004803603602081101561055257600080fd5b50356001600160a01b031661198c565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561059c578181015183820152602001610584565b50505050905090810190601f1680156105c95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101ca600480360360608110156105ed57600080fd5b810190602081018135600160201b81111561060757600080fd5b82018360208201111561061957600080fd5b803590602001918460018302840111600160201b8311171561063a57600080fd5b91935091506001600160a01b038135169060200135611ae2565b6105286004803603602081101561066a57600080fd5b810190602081018135600160201b81111561068457600080fd5b82018360208201111561069657600080fd5b803590602001918460018302840111600160201b831117156106b757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612215945050505050565b6102836004803603602081101561070e57600080fd5b5035612390565b6105286004803603604081101561072b57600080fd5b810190602081018135600160201b81111561074557600080fd5b82018360208201111561075757600080fd5b803590602001918460018302840111600160201b8311171561077857600080fd5b9193509150356001600160a01b031661257a565b6101ca600480360360408110156107a257600080fd5b810190602081018135600160201b8111156107bc57600080fd5b8201836020820111156107ce57600080fd5b803590602001918460018302840111600160201b831117156107ef57600080fd5b919390929091602081019035600160201b81111561080c57600080fd5b82018360208201111561081e57600080fd5b803590602001918460018302840111600160201b8311171561083f57600080fd5b509092509050612bcc565b6101ca6004803603608081101561086057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561088a57600080fd5b82018360208201111561089c57600080fd5b803590602001918460018302840111600160201b831117156108bd57600080fd5b919390929091602081019035600160201b8111156108da57600080fd5b8201836020820111156108ec57600080fd5b803590602001918460018302840111600160201b8311171561090d57600080fd5b919350915035612cb8565b6105286004803603606081101561092e57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561095857600080fd5b82018360208201111561096a57600080fd5b803590602001918460018302840111600160201b8311171561098b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156109dd57600080fd5b8201836020820111156109ef57600080fd5b803590602001918460018302840111600160201b83111715610a1057600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061302a945050505050565b61039460048036036020811015610a6757600080fd5b50356001600160a01b0316613591565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610ac457600080fd5b505afa158015610ad8573d6000803e3d6000fd5b505050506040513d6020811015610aee57600080fd5b50516001600160a01b03163314610b435760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014158015610db457506040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015610cc15780601f10610c9657610100808354040283529160200191610cc1565b820191906000526020600020905b815481529060010190602001808311610ca457829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040526040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610d69578181015183820152602001610d51565b50505050905090810190601f168015610d965780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012014155b1515610df457604051600160e51b62461bcd028152600401808060200182810382526040815260200180613bfb6040913960400191505060405180910390fd5b610e6d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250600292508791506135ee9050565b505050505050565b6000805460408051600160e41b62e32cf9028152905183926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610ebc57600080fd5b505afa158015610ed0573d6000803e3d6000fd5b505050506040513d6020811015610ee657600080fd5b50516001600160a01b03163314610f3b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b610f7a84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061221592505050565b156113f357600061100260066000878760405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a90046001600160a01b03166139c8565b9050600660018281548110151561101557fe5b906000526020600020906005020160030181905550600060018281548110151561103b57fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc77660018281548110151561109357fe5b6000918252602090912060059091020154600180546001600160a01b0390921691849081106110be57fe5b90600052602060002090600502016001016001848154811015156110de57fe5b90600052602060002090600502016002016001858154811015156110fe57fe5b60009182526020909120600460059092020101546001805460ff909216918790811061112657fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156111d95780601f106111ae576101008083540402835291602001916111d9565b820191906000526020600020905b8154815290600101906020018083116111bc57829003601f168201915b505083810382528654600260001961010060018416150201909116048082526020909101908790801561124d5780601f106112225761010080835404028352916020019161124d565b820191906000526020600020905b81548152906001019060200180831161123057829003601f168201915b505097505050505050505060405180910390a160408051602080820190815260048054600260001961010060018416150201909116049383018490529290918291606090910190849080156112e35780601f106112b8576101008083540402835291602001916112e3565b820191906000526020600020905b8154815290600101906020018083116112c657829003601f168201915b5050925050506040516020818303038152906040528051906020012060018281548110151561130e57fe5b6000918252602091829020604080518085019485526002600590940290920183018054600019610100600183161502011693909304908201819052919291829160600190849080156113a15780601f10611376576101008083540402835291602001916113a1565b820191906000526020600020905b81548152906001019060200180831161138457829003601f168201915b50509250505060405160208183030381529060405280519060200120146001828154811015156113cd57fe5b60009182526020909120600590910201549093506001600160a01b031691506113fa9050565b5060009050805b9250929050565b6001600160a01b038116600090815260026020526040812054606090819083908190151561146857505060408051808201825260048152600160e01b634e4f4e45026020808301919091528251908101909252600080835286955090935090915080611650565b6000611473876139c8565b905060018181548110151561148457fe5b6000918252602090912060059091020154600180546001600160a01b0390921691839081106114af57fe5b90600052602060002090600502016001016001838154811015156114cf57fe5b90600052602060002090600502016002016001848154811015156114ef57fe5b90600052602060002090600502016003015460018581548110151561151057fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff909216928691908301828280156115af5780601f10611584576101008083540402835291602001916115af565b820191906000526020600020905b81548152906001019060200180831161159257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529599508894509250840190508282801561163d5780601f106116125761010080835404028352916020019161163d565b820191906000526020600020905b81548152906001019060200180831161162057829003601f168201915b5050505050925095509550955095509550505b91939590929450565b6001545b90565b6001600160a01b038116600090815260026020526040902054606090819015156116bd57604051806040016040528060048152602001600160e01b634e4f4e4502815250604051806020016040528060008152509150915061182c565b60006116c8846139c8565b90506001818154811015156116d957fe5b90600052602060002090600502016001016001828154811015156116f957fe5b6000918252602091829020835460408051601f600260001961010060018716150201909416849004908101879004870282018701909252818152600590940290920101928491908301828280156117915780601f1061176657610100808354040283529160200191611791565b820191906000526020600020905b81548152906001019060200180831161177457829003601f168201915b5050845460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529597508694509250840190508282801561181f5780601f106117f45761010080835404028352916020019161181f565b820191906000526020600020905b81548152906001019060200180831161180257829003601f168201915b5050505050905092509250505b915091565b6001600160a01b038316600090815260026020526040812054151561185857506001611985565b6000611863856139c8565b9050838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001206001828154811015156118c757fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156119645780601f1061193957610100808354040283529160200191611964565b820191906000526020600020905b81548152906001019060200180831161194757829003601f168201915b50509250505060405160208183030381529060405280519060200120149150505b9392505050565b6001600160a01b03811660009081526002602052604090205460609015156119d257506040805180820190915260048152600160e01b634e4f4e45026020820152611add565b60006119dd836139c8565b90506001818154811015156119ee57fe5b9060005260206000209060050201600301546000141515611abc576001805482908110611a1757fe5b600091825260209182902060026005909202018101805460408051601f600019610100600186161502019093169490940491820185900485028401850190528083529192909190830182828015611aaf5780601f10611a8457610100808354040283529160200191611aaf565b820191906000526020600020905b815481529060010190602001808311611a9257829003601f168201915b5050505050915050611add565b50506040805180820190915260048152600160e01b634e4f4e450260208201525b919050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611b2f57600080fd5b505afa158015611b43573d6000803e3d6000fd5b505050506040513d6020811015611b5957600080fd5b50516001600160a01b03163314611bae5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506001600160a01b03871681526002602052604090205486935015159150611c5390505760408051600160e51b62461bcd02815260206004820152601760248201527f6163636f756e7420646f6573206e6f7420657869737473000000000000000000604482015290519081900360640190fd5b816040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611c94578181015183820152602001611c7c565b50505050905090810190601f168015611cc15780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001206001611ce7836139c8565b81548110611cf157fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611d8e5780601f10611d6357610100808354040283529160200191611d8e565b820191906000526020600020905b815481529060010190602001808311611d7157829003601f168201915b50509250505060405160208183030381529060405280519060200120141515611e015760408051600160e51b62461bcd02815260206004820152601860248201527f6163636f756e7420696e20646966666572656e74206f72670000000000000000604482015290519081900360640190fd5b600083118015611e115750600683105b1515611e675760408051600160e51b62461bcd02815260206004820152601d60248201527f696e76616c696420737461747573206368616e67652072657175657374000000604482015290519081900360640190fd5b611eb58487878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506040805160208101909152908152925061302a915050565b151560011415611ef957604051600160e51b62461bcd028152600401808060200182810382526031815260200180613c6f6031913960400191505060405180910390fd5b60008360011415611f76576001611f0f866139c8565b81548110611f1957fe5b9060005260206000209060050201600301546002141515611f6e57604051600160e51b62461bcd028152600401808060200182810382526039815260200180613b9a6039913960400191505060405180910390fd5b50600461215f565b8360021415611ff1576001611f8a866139c8565b81548110611f9457fe5b9060005260206000209060050201600301546004141515611fe957604051600160e51b62461bcd02815260040180806020018281038252603c815260200180613b5e603c913960400191505060405180910390fd5b50600261215f565b836003141561206d576001612005866139c8565b8154811061200f57fe5b90600052602060002090600502016003015460051415151561206557604051600160e51b62461bcd028152600401808060200182810382526038815260200180613b266038913960400191505060405180910390fd5b50600561215f565b83600414156120e8576001612081866139c8565b8154811061208b57fe5b90600052602060002090600502016003015460051415156120e057604051600160e51b62461bcd028152600401808060200182810382526034815260200180613c3b6034913960400191505060405180910390fd5b50600761215f565b836005141561215f5760016120fc866139c8565b8154811061210657fe5b906000526020600020906005020160030154600714151561215b57604051600160e51b62461bcd028152600401808060200182810382526038815260200180613aee6038913960400191505060405180910390fd5b5060025b80600161216b876139c8565b8154811061217557fe5b9060005260206000209060050201600301819055507f36b0ea38154dec5e98b6bf928b971a9db5e8cd4b6946350e9e43fb9848c70b258588888460405180856001600160a01b03166001600160a01b03168152602001806020018381526020018281038252858582818152602001925080828437600083820152604051601f909101601f191690920182900397509095505050505050a150505050505050565b6000806001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561226657818101518382015260200161224e565b50505050905090810190601f1680156122935780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b03161461238857600060066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156123125781810151838201526020016122fa565b50505050905090810190601f16801561233f5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316905061237d81613591565b600214915050611add565b506000919050565b60006060806000806001868154811015156123a757fe5b6000918252602090912060059091020154600180546001600160a01b0390921691889081106123d257fe5b90600052602060002090600502016001016001888154811015156123f257fe5b906000526020600020906005020160020160018981548110151561241257fe5b90600052602060002090600502016003015460018a81548110151561243357fe5b600091825260209182902060046005909202010154845460408051601f6002600019600186161561010002019094169390930492830185900485028101850190915281815260ff909216928691908301828280156124d25780601f106124a7576101008083540402835291602001916124d2565b820191906000526020600020905b8154815290600101906020018083116124b557829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156125605780601f1061253557610100808354040283529160200191612560565b820191906000526020600020905b81548152906001019060200180831161254357829003601f168201915b505050505092509450945094509450945091939590929450565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156125c957600080fd5b505afa1580156125dd573d6000803e3d6000fd5b505050506040513d60208110156125f357600080fd5b50516001600160a01b031633146126485760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60606126538361198c565b9050600061266084613591565b9050600061266d856139c8565b604080516020808201908152600580546002600019610100600184161502019091160493830184905293945091829160600190849080156126ef5780601f106126c4576101008083540402835291602001916126ef565b820191906000526020600020905b8154815290600101906020018083116126d257829003601f168201915b50509250505060405160208183030381529060405280519060200120836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561274c578181015183820152602001612734565b50505050905090810190601f1680156127795780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001201480156127a15750816001145b15612831578460066000898960405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b600260018281548110151561284257fe5b9060005260206000209060050201600301819055506001808281548110151561286757fe5b906000526020600020906005020160040160006101000a81548160ff0219169083151502179055507f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776856001838154811015156128c057fe5b90600052602060002090600502016001016001848154811015156128e057fe5b906000526020600020906005020160020160018581548110151561290057fe5b60009182526020909120600460059092020101546001805460ff909216918790811061292857fe5b600091825260209182902060036005909202010154604080516001600160a01b038816815284151560608201526080810183905260a0938101848152875460026000196101006001841615020190911604948201859052929390929183019060c0840190889080156129db5780601f106129b0576101008083540402835291602001916129db565b820191906000526020600020905b8154815290600101906020018083116129be57829003601f168201915b5050838103825286546002600019610100600184161502019091160480825260209091019087908015612a4f5780601f10612a2457610100808354040283529160200191612a4f565b820191906000526020600020905b815481529060010190602001808311612a3257829003601f168201915b505097505050505050505060405180910390a16040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015612ae55780601f10612aba57610100808354040283529160200191612ae5565b820191906000526020600020905b815481529060010190602001808311612ac857829003601f168201915b50509250505060405160208183030381529060405280519060200120600182815481101515612b1057fe5b600091825260209182902060408051808501948552600260059094029092018301805460001961010060018316150201169390930490820181905291929182916060019084908015612ba35780601f10612b7857610100808354040283529160200191612ba3565b820191906000526020600020905b815481529060010190602001808311612b8657829003601f168201915b505092505050604051602081830303815290604052805190602001201493505050509392505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015612c1957600080fd5b505afa158015612c2d573d6000803e3d6000fd5b505050506040513d6020811015612c4357600080fd5b50516001600160a01b03163314612c985760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b612ca4600485856139e7565b50612cb1600583836139e7565b5050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015612d0557600080fd5b505afa158015612d19573d6000803e3d6000fd5b505050506040513d6020811015612d2f57600080fd5b50516001600160a01b03163314612d845760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6040805160208082019081526005805460026000196101006001841615020190911604938301849052929091829160609091019084908015612e075780601f10612ddc57610100808354040283529160200191612e07565b820191906000526020600020905b815481529060010190602001808311612dea57829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001201480612f7157506040805160208082019081526004805460026000196101006001841615020190911604938301849052929091829160609091019084908015612f005780601f10612ed557610100808354040283529160200191612f00565b820191906000526020600020905b815481529060010190602001808311612ee357829003601f168201915b50509250505060405160208183030381529060405280519060200120838360405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120145b1515612fb157604051600160e51b62461bcd028152600401808060200182810382526028815260200180613bd36028913960400191505060405180910390fd5b610e6d8686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092019190915250879250600191506135ee9050565b604080516020808201908152600480546002600019610100600184161502019091160493830184905260009390928291606090910190849080156130af5780601f10613084576101008083540402835291602001916130af565b820191906000526020600020905b81548152906001019060200180831161309257829003601f168201915b505092505050604051602081830303815290604052805190602001206130d48561198c565b6040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156131145781810151838201526020016130fc565b50505050905090810190601f1680156131415780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120141561342257600061316d856139c8565b9050836040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156131b0578181015183820152602001613198565b50505050905090810190601f1680156131dd5780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561320757fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156132a45780601f10613279576101008083540402835291602001916132a4565b820191906000526020600020905b81548152906001019060200180831161328757829003601f168201915b50509250505060405160208183030381529060405280519060200120148061341a5750826040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156133085781810151838201526020016132f0565b50505050905090810190601f1680156133355780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561335f57fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156133fc5780601f106133d1576101008083540402835291602001916133fc565b820191906000526020600020905b8154815290600101906020018083116133df57829003601f168201915b50509250505060405160208183030381529060405280519060200120145b915050611985565b836001600160a01b031660066000856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015613471578181015183820152602001613459565b50505050905090810190601f16801561349e5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b031614806135895750836001600160a01b031660066000846040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561352757818101518382015260200161350f565b50505050905090810190601f1680156135545780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020546001600160a01b0316145b949350505050565b6001600160a01b03811660009081526002602052604081205415156135b857506000611add565b60006135c3836139c8565b90506001818154811015156135d457fe5b906000526020600020906005020160030154915050919050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561363b57600080fd5b505afa15801561364f573d6000803e3d6000fd5b505050506040513d602081101561366557600080fd5b50516001600160a01b031633146136ba5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60006136c5866139c8565b6001600160a01b0387166000908152600260205260409020549091501561377757836001828154811015156136f657fe5b9060005260206000209060050201600201908051906020019061371a929190613a65565b508260018281548110151561372b57fe5b9060005260206000209060050201600301819055508160018281548110151561375057fe5b60009182526020909120600590910201600401805460ff1916911515919091179055613892565b600380546001908101918290556001600160a01b03888116600081815260026020908152604080832096909655855160a0810187529283528281018b81529583018a905260608301899052871515608084015284548086018087559590925282517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6600590930292830180546001600160a01b03191691909516178455945180519495929461384f937fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7909301929190910190613a65565b506040820151805161386b916002840191602090910190613a65565b50606082015160038201556080909101516004909101805460ff1916911515919091179055505b7f68e62a03aeb0a125c2fc869eed72f2fca473680987bdd680c093a534e17cc776868686858760405180866001600160a01b03166001600160a01b03168152602001806020018060200185151515158152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015613921578181015183820152602001613909565b50505050905090810190601f16801561394e5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015613981578181015183820152602001613969565b50505050905090810190601f1680156139ae5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a1505050505050565b6001600160a01b03166000908152600260205260409020546000190190565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613a285782800160ff19823516178555613a55565b82800160010185558215613a55579182015b82811115613a55578235825591602001919060010190613a3a565b50613a61929150613ad3565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613aa657805160ff1916838001178555613a55565b82800160010185558215613a55579182015b82811115613a55578251825591602001919060010190613ab8565b61165d91905b80821115613a615760008155600101613ad956fe6163636f756e74207265636f76657279206e6f7420696e697469617465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e7420697320616c726561647920626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e2073757370656e646564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e656163636f756e74206973206e6f7420696e20616374697665207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e6563616e2062652063616c6c656420746f2061737369676e2061646d696e20726f6c6573206f6e6c7963616e6e6f742062652063616c6c65642066726f2061737369676e696e67206f72672061646d696e20616e64206e6574776f726b2061646d696e20726f6c65736163636f756e74206973206e6f7420626c61636b6c69737465642e206f7065726174696f6e2063616e6e6f7420626520646f6e65737461747573206368616e6765206e6f7420706f737369626c6520666f72206f72672061646d696e206163636f756e7473a165627a7a72305820f5cf4f43c37e9dae763b0a301bd310a91d3b733b1642aabb6208a69fcc6a272d0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/NodeManager.abi b/permission/v2/contract/gen/NodeManager.abi new file mode 100644 index 0000000000..a4955259a5 --- /dev/null +++ b/permission/v2/contract/gen/NodeManager.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateNodeStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"enodeId","type":"string"}],"name":"getNodeDetails","outputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_nodeStatus","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_orgId","type":"string"}],"name":"addAdminNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"}],"name":"connectionAllowed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_orgId","type":"string"}],"name":"addOrgNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_orgId","type":"string"}],"name":"addNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_nodeIndex","type":"uint256"}],"name":"getNodeDetailsFromIndex","outputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_nodeStatus","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfNodes","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_orgId","type":"string"}],"name":"approveNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeDeactivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeBlacklisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeRecoveryInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_enodeId","type":"string"},{"indexed":false,"name":"_ip","type":"string"},{"indexed":false,"name":"_port","type":"uint16"},{"indexed":false,"name":"_raftport","type":"uint16"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"NodeRecoveryCompleted","type":"event"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/NodeManager.bin b/permission/v2/contract/gen/NodeManager.bin new file mode 100644 index 0000000000..e0598dfb5f --- /dev/null +++ b/permission/v2/contract/gen/NodeManager.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5060405160208061338f8339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905561332d806100626000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c80634c573311116100665780634c5733111461042b578063549583df1461073057806397c07a9b146108f2578063b81c806a1461090f578063f82e08ac1461092957610093565b806337d50b27146100985780633f0e0e471461025e5780634530abe11461042b57806345a59e5b146105ed575b600080fd5b61025c600480360360c08110156100ae57600080fd5b810190602081018135600160201b8111156100c857600080fd5b8201836020820111156100da57600080fd5b803590602001918460018302840111600160201b831117156100fb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561014d57600080fd5b82018360208201111561015f57600080fd5b803590602001918460018302840111600160201b8311171561018057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b8111156101e657600080fd5b8201836020820111156101f857600080fd5b803590602001918460018302840111600160201b8311171561021957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250610aeb915050565b005b6102cc6004803603602081101561027457600080fd5b810190602081018135600160201b81111561028e57600080fd5b8201836020820111156102a057600080fd5b803590602001918460018302840111600160201b831117156102c157600080fd5b50909250905061160a565b6040805161ffff80861660608301528416608082015260a0810183905260c080825288519082015287519091829160208084019284019160e08501918c019080838360005b83811015610329578181015183820152602001610311565b50505050905090810190601f1680156103565780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b83811015610389578181015183820152602001610371565b50505050905090810190601f1680156103b65780820380516001836020036101000a031916815260200191505b5084810382528851815288516020918201918a019080838360005b838110156103e95781810151838201526020016103d1565b50505050905090810190601f1680156104165780820380516001836020036101000a031916815260200191505b50995050505050505050505060405180910390f35b61025c600480360360a081101561044157600080fd5b810190602081018135600160201b81111561045b57600080fd5b82018360208201111561046d57600080fd5b803590602001918460018302840111600160201b8311171561048e57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156104e057600080fd5b8201836020820111156104f257600080fd5b803590602001918460018302840111600160201b8311171561051357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b81111561057957600080fd5b82018360208201111561058b57600080fd5b803590602001918460018302840111600160201b831117156105ac57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506119d1945050505050565b61071c6004803603606081101561060357600080fd5b810190602081018135600160201b81111561061d57600080fd5b82018360208201111561062f57600080fd5b803590602001918460018302840111600160201b8311171561065057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156106a257600080fd5b8201836020820111156106b457600080fd5b803590602001918460018302840111600160201b831117156106d557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903561ffff169150611ec79050565b604080519115158252519081900360200190f35b61025c600480360360a081101561074657600080fd5b810190602081018135600160201b81111561076057600080fd5b82018360208201111561077257600080fd5b803590602001918460018302840111600160201b8311171561079357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156107e557600080fd5b8201836020820111156107f757600080fd5b803590602001918460018302840111600160201b8311171561081857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b81111561087e57600080fd5b82018360208201111561089057600080fd5b803590602001918460018302840111600160201b831117156108b157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506121e8945050505050565b6102cc6004803603602081101561090857600080fd5b50356125d5565b610917612879565b60408051918252519081900360200190f35b61025c600480360360a081101561093f57600080fd5b810190602081018135600160201b81111561095957600080fd5b82018360208201111561096b57600080fd5b803590602001918460018302840111600160201b8311171561098c57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156109de57600080fd5b8201836020820111156109f057600080fd5b803590602001918460018302840111600160201b83111715610a1157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929561ffff8535811696602087013590911695919450925060608101915060400135600160201b811115610a7757600080fd5b820183602082011115610a8957600080fd5b803590602001918460018302840111600160201b83111715610aaa57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612880945050505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610b3857600080fd5b505afa158015610b4c573d6000803e3d6000fd5b505050506040513d6020811015610b6257600080fd5b50516001600160a01b03163314610bb75760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8560036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610bfd578181015183820152602001610be5565b50505050905090810190601f168015610c2a5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020541515610caa5760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b610cb48784612eed565b1515610cf457604051600160e51b62461bcd02815260040180806020018281038252602a815260200180613265602a913960400191505060405180910390fd5b8160011480610d035750816002145b80610d0e5750816003145b80610d195750816004145b80610d245750816005145b1515610d6457604051600160e51b62461bcd0281526004018080602001828103825260268152602001806132af6026913960400191505060405180910390fd5b6000610d6f88613047565b9050866040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015610db2578181015183820152602001610d9a565b50505050905090810190601f168015610ddf5780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120600182815481101515610e0957fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610ea65780601f10610e7b57610100808354040283529160200191610ea6565b820191906000526020600020905b815481529060010190602001808311610e8957829003601f168201915b50509250505060405160208183030381529060405280519060200120141580610ef957508561ffff16600182815481101515610ede57fe5b600091825260209091206002600590920201015461ffff1614155b80610f3457508461ffff16600182815481101515610f1357fe5b600091825260209091206005909102016002015462010000900461ffff1614155b15610f3f5750611601565b826001141561114857610f51886130f0565b600214610f965760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b6003600182815481101515610fa757fe5b9060005260206000209060050201600401819055507ff631019be71bc682c59150635d714061185232e98e60de8bdd87bbee239cc5c888888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360005b8381101561104357818101518382015260200161102b565b50505050905090810190601f1680156110705780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b838110156110a357818101518382015260200161108b565b50505050905090810190601f1680156110d05780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156111035781810151838201526020016110eb565b50505050905090810190601f1680156111305780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a16115ff565b826002141561124b5761115a886130f0565b60031461119f5760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b60026001828154811015156111b057fe5b9060005260206000209060050201600401819055507ffb98f62dea866f0c373574c8523f611d0db1d8f19cc1b95d07a221d36a6a45de88888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360008381101561104357818101518382015260200161102b565b826003141561130057600460018281548110151561126557fe5b9060005260206000209060050201600401819055507f25300d4d785e654bc9b7979700cfa0fdc9ace890a46841fecfce661fd2c41a3388888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360008381101561104357818101518382015260200161102b565b826004141561140357611312886130f0565b6004146113575760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b600560018281548110151561136857fe5b9060005260206000209060050201600401819055507f72779f66ea90e28bae76fbfe03eaef5ae01699976c7493f93186ab9560ccfaa488888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360008381101561104357818101518382015260200161102b565b61140c886130f0565b6005146114515760408051600160e51b62461bcd02815260206004820152601d602482015260008051602061328f833981519152604482015290519081900360640190fd5b600260018281548110151561146257fe5b9060005260206000209060050201600401819055507f60aac8c36efdaabf125dc9ec2124bde8b3ceafe5c8b4fc8063fc4ac9017eb0be88888888886040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360005b838110156114fe5781810151838201526020016114e6565b50505050905090810190601f16801561152b5780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b8381101561155e578181015183820152602001611546565b50505050905090810190601f16801561158b5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156115be5781810151838201526020016115a6565b50505050905090810190601f1680156115eb5780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a15b505b50505050505050565b6060806060600080600060026000866040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611659578181015183820152602001611641565b50505050905090810190601f1680156116865780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054600014156116f157505060408051602080820183526000808352835180830185528181528451928301909452808252919650919450909250905080806119c7565b600061173289898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061304792505050565b905060018181548110151561174357fe5b906000526020600020906005020160030160018281548110151561176357fe5b906000526020600020906005020160000160018381548110151561178357fe5b90600052602060002090600502016001016001848154811015156117a357fe5b60009182526020909120600260059092020101546001805461ffff90921691869081106117cc57fe5b906000526020600020906005020160020160029054906101000a900461ffff166001868154811015156117fb57fe5b600091825260209182902060046005909202010154865460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928891908301828280156118965780601f1061186b57610100808354040283529160200191611896565b820191906000526020600020905b81548152906001019060200180831161187957829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a9450925084019050828280156119245780601f106118f957610100808354040283529160200191611924565b820191906000526020600020905b81548152906001019060200180831161190757829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a50899450925084019050828280156119b25780601f10611987576101008083540402835291602001916119b2565b820191906000526020600020905b81548152906001019060200180831161199557829003601f168201915b50505050509350965096509650965096509650505b9295509295509295565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611a1e57600080fd5b505afa158015611a32573d6000803e3d6000fd5b505050506040513d6020811015611a4857600080fd5b50516001600160a01b03163314611a9d5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8460036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611ae3578181015183820152602001611acb565b50505050905090810190601f168015611b105780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181528151602092830120865290850195909552505050016000205415611b8f5760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b60048054600101908190556040805160208082018181528a519383019390935289516003936000938c9391928392606090920191850190808383895b83811015611be3578181015183820152602001611bcb565b50505050905090810190601f168015611c105780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652858201969096529385016000908120969096555050825160c0810184528a81528083018a905261ffff808a169482019490945292871660608401525060808201859052600260a083015260018054808201808355919094528251805191946005027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60192611cbe928492909101906131cc565b506020828101518051611cd792600185019201906131cc565b506040820151600282018054606085015161ffff908116620100000263ffff0000199190941661ffff19909216919091171691909117905560808201518051611d2a9160038401916020909101906131cc565b5060a082015181600401555050507f9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d86868686866040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818151815260200191508051906020019080838360005b83811015611dbf578181015183820152602001611da7565b50505050905090810190601f168015611dec5780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015611e1f578181015183820152602001611e07565b50505050905090810190601f168015611e4c5780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015611e7f578181015183820152602001611e67565b50505050905090810190601f168015611eac5780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a1505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611f1657600080fd5b505afa158015611f2a573d6000803e3d6000fd5b505050506040513d6020811015611f4057600080fd5b50516001600160a01b03163314611f955760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60036000856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611fda578181015183820152602001611fc2565b50505050905090810190601f1680156120075780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012081526020019081526020016000205460001415612041575060006121e1565b600061204c85613047565b905060018181548110151561205d57fe5b90600052602060002090600502016004015460021480156121cc5750836040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156120ba5781810151838201526020016120a2565b50505050905090810190601f1680156120e75780820380516001836020036101000a031916815260200191505b50925050506040516020818303038152906040528051906020012060018281548110151561211157fe5b906000526020600020906005020160010160405160200180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156121ae5780601f10612183576101008083540402835291602001916121ae565b820191906000526020600020905b81548152906001019060200180831161219157829003601f168201915b50509250505060405160208183030381529060405280519060200120145b156121db5760019150506121e1565b60009150505b9392505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561223557600080fd5b505afa158015612249573d6000803e3d6000fd5b505050506040513d602081101561225f57600080fd5b50516001600160a01b031633146122b45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8460036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b838110156122fa5781810151838201526020016122e2565b50505050905090810190601f1680156123275780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652908501959095525050500160002054156123a65760408051600160e51b62461bcd02815260206004820152601660248201527f70617373656420656e6f64652069642065786973747300000000000000000000604482015290519081900360640190fd5b60048054600101908190556040805160208082018181528a519383019390935289516003936000938c9391928392606090920191850190808383895b838110156123fa5781810151838201526020016123e2565b50505050905090810190601f1680156124275780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208652858201969096529385016000908120969096555050825160c0810184528a81528083018a905261ffff808a169482019490945292871660608401525060808201859052600160a083018190528054808201808355919094528251805191946005027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601926124d5928492909101906131cc565b5060208281015180516124ee92600185019201906131cc565b506040820151600282018054606085015161ffff908116620100000263ffff0000199190941661ffff199092169190911716919091179055608082015180516125419160038401916020909101906131cc565b5060a082015181600401555050507ff9bad9f8a2dccc52fad61273a7fd673335b420319506c19b87df9ce7a19732da86868686866040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff168152602001806020018481038452898181518152602001915080519060200190808383600083811015611dbf578181015183820152602001611da7565b606080606060008060006001878154811015156125ee57fe5b906000526020600020906005020160030160018881548110151561260e57fe5b906000526020600020906005020160000160018981548110151561262e57fe5b906000526020600020906005020160010160018a81548110151561264e57fe5b60009182526020909120600260059092020101546001805461ffff909216918c90811061267757fe5b906000526020600020906005020160020160029054906101000a900461ffff1660018c8154811015156126a657fe5b600091825260209182902060046005909202010154865460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928891908301828280156127415780601f1061271657610100808354040283529160200191612741565b820191906000526020600020905b81548152906001019060200180831161272457829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a9450925084019050828280156127cf5780601f106127a4576101008083540402835291602001916127cf565b820191906000526020600020905b8154815290600101906020018083116127b257829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a508994509250840190508282801561285d5780601f106128325761010080835404028352916020019161285d565b820191906000526020600020905b81548152906001019060200180831161284057829003601f168201915b5050505050935095509550955095509550955091939550919395565b6004545b90565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156128cd57600080fd5b505afa1580156128e1573d6000803e3d6000fd5b505050506040513d60208110156128f757600080fd5b50516001600160a01b0316331461294c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8460036000826040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561299257818101518382015260200161297a565b50505050905090810190601f1680156129bf5780820380516001836020036101000a031916815260200191505b5060408051601f19818403018152918152815160209283012086529085019590955250505001600020541515612a3f5760408051600160e51b62461bcd02815260206004820152601e60248201527f70617373656420656e6f646520696420646f6573206e6f742065786973740000604482015290519081900360640190fd5b612a498683612eed565b1515612a8957604051600160e51b62461bcd02815260040180806020018281038252602d8152602001806132d5602d913960400191505060405180910390fd5b612a92866130f0565b600114612ae95760408051600160e51b62461bcd02815260206004820152601c60248201527f6e6f7468696e672070656e64696e6720666f7220617070726f76616c00000000604482015290519081900360640190fd5b6000612af487613047565b9050856040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612b37578181015183820152602001612b1f565b50505050905090810190601f168015612b645780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120600182815481101515612b8e57fe5b90600052602060002090600502016001016040516020018080602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015612c2b5780601f10612c0057610100808354040283529160200191612c2b565b820191906000526020600020905b815481529060010190602001808311612c0e57829003601f168201915b50509250505060405160208183030381529060405280519060200120141580612c7e57508461ffff16600182815481101515612c6357fe5b600091825260209091206002600590920201015461ffff1614155b80612cb957508361ffff16600182815481101515612c9857fe5b600091825260209091206005909102016002015462010000900461ffff1614155b15612cc45750612ee5565b6002600182815481101515612cd557fe5b9060005260206000209060050201600401819055507f9394c836a3325586270659f6aa3b9f835abca9afe7fec5abfc69760bb12bce0d600182815481101515612d1a57fe5b9060005260206000209060050201600001878787600186815481101515612d3d57fe5b90600052602060002090600502016003016040518080602001806020018661ffff1661ffff1681526020018561ffff1661ffff16815260200180602001848103845289818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015612dfb5780601f10612dd057610100808354040283529160200191612dfb565b820191906000526020600020905b815481529060010190602001808311612dde57829003601f168201915b505084810383528851815288516020918201918a019080838360005b83811015612e2f578181015183820152602001612e17565b50505050905090810190601f168015612e5c5780820380516001836020036101000a031916815260200191505b50848103825285546002600019610100600184161502019091160480825260209091019086908015612ecf5780601f10612ea457610100808354040283529160200191612ecf565b820191906000526020600020905b815481529060010190602001808311612eb257829003601f168201915b50509850505050505050505060405180910390a1505b505050505050565b6000816040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015612f30578181015183820152602001612f18565b50505050905090810190601f168015612f5d5780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001206001612f8385613047565b81548110612f8d57fe5b60009182526020918290206040805180850194855260036005909402909201929092018054600260001961010060018416150201909116049282018390529291829160600190849080156130225780601f10612ff757610100808354040283529160200191613022565b820191906000526020600020905b81548152906001019060200180831161300557829003601f168201915b5050925050506040516020818303038152906040528051906020012014905092915050565b6000600160036000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015613090578181015183820152602001613078565b50505050905090810190601f1680156130bd5780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001208152602001908152602001600020540390505b919050565b600060036000836040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561313757818101518382015260200161311f565b50505050905090810190601f1680156131645780820380516001836020036101000a031916815260200191505b5092505050604051602081830303815290604052805190602001208152602001908152602001600020546000141561319e575060006130eb565b60016131a983613047565b815481106131b357fe5b9060005260206000209060050201600401549050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061320d57805160ff191683800117855561323a565b8280016001018555821561323a579182015b8281111561323a57825182559160200191906001019061321f565b5061324692915061324a565b5090565b61287d91905b80821115613246576000815560010161325056fe656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f72676f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000696e76616c6964206f7065726174696f6e2e2077726f6e6720616374696f6e20706173736564656e6f646520696420646f6573206e6f742062656c6f6e6720746f2074686520706173736564206f7267206964a165627a7a7230582033fd8af5439a9af79764088da6847651cc33d3b41698fff5d54b3e47419c5c6a0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/OrgManager.abi b/permission/v2/contract/gen/OrgManager.abi new file mode 100644 index 0000000000..f780769fe1 --- /dev/null +++ b/permission/v2/contract/gen/OrgManager.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateOrg","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"approveOrgStatusUpdate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getUltimateParent","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_pOrgId","type":"string"},{"name":"_orgId","type":"string"}],"name":"addSubOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"checkOrgActive","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_orgIndex","type":"uint256"}],"name":"getOrgInfo","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getSubOrgIndexes","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfOrgs","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"},{"name":"_orgStatus","type":"uint256"}],"name":"checkOrgStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_breadth","type":"uint256"},{"name":"_depth","type":"uint256"}],"name":"setUpOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"}],"name":"approveOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getOrgDetails","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"}],"name":"addOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"checkOrgExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"OrgApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"},{"indexed":false,"name":"_status","type":"uint256"}],"name":"OrgPendingApproval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"}],"name":"OrgSuspended","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_porgId","type":"string"},{"indexed":false,"name":"_ultParent","type":"string"},{"indexed":false,"name":"_level","type":"uint256"}],"name":"OrgSuspensionRevoked","type":"event"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/OrgManager.bin b/permission/v2/contract/gen/OrgManager.bin new file mode 100644 index 0000000000..22233141d8 --- /dev/null +++ b/permission/v2/contract/gen/OrgManager.bin @@ -0,0 +1 @@ +608060405260018054600160a01b60ff021916905560046002819055600355600060065534801561002f57600080fd5b50604051602080613bb58339810180604052602081101561004f57600080fd5b5051600180546001600160a01b0319166001600160a01b03909216919091179055613b368061007f6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80637755ebdd1161008c578063e302831611610066578063e302831614610787578063f4d6d9f5146107f5578063f9953de514610863578063ffe40d1d146108d1576100ea565b80637755ebdd146106655780638c8642df1461066d5780639e58eb9f14610713576100ea565b80631f953480116100c85780631f953480146102c25780633fd62ae7146103805780635c4f32ee146104385780635e99f6e5146105a7576100ea565b80630cc27493146100ef57806314f775f91461016f578063177c8d8a146101df575b600080fd5b61015d6004803603604081101561010557600080fd5b810190602081018135600160201b81111561011f57600080fd5b82018360208201111561013157600080fd5b803590602001918460018302840111600160201b8311171561015257600080fd5b919350915035610975565b60408051918252519081900360200190f35b6101dd6004803603604081101561018557600080fd5b810190602081018135600160201b81111561019f57600080fd5b8201836020820111156101b157600080fd5b803590602001918460018302840111600160201b831117156101d257600080fd5b919350915035610d07565b005b61024d600480360360208110156101f557600080fd5b810190602081018135600160201b81111561020f57600080fd5b82018360208201111561022157600080fd5b803590602001918460018302840111600160201b8311171561024257600080fd5b509092509050610ef9565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561028757818101518382015260200161026f565b50505050905090810190601f1680156102b45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101dd600480360360408110156102d857600080fd5b810190602081018135600160201b8111156102f257600080fd5b82018360208201111561030457600080fd5b803590602001918460018302840111600160201b8311171561032557600080fd5b919390929091602081019035600160201b81111561034257600080fd5b82018360208201111561035457600080fd5b803590602001918460018302840111600160201b8311171561037557600080fd5b5090925090506110ae565b6104246004803603602081101561039657600080fd5b810190602081018135600160201b8111156103b057600080fd5b8201836020820111156103c257600080fd5b803590602001918460018302840111600160201b831117156103e357600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061128c945050505050565b604080519115158252519081900360200190f35b6104556004803603602081101561044e57600080fd5b5035611499565b60405180806020018060200180602001868152602001858152602001848103845289818151815260200191508051906020019080838360005b838110156104a657818101518382015260200161048e565b50505050905090810190601f1680156104d35780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b838110156105065781810151838201526020016104ee565b50505050905090810190601f1680156105335780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b8381101561056657818101518382015260200161054e565b50505050905090810190601f1680156105935780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610615600480360360208110156105bd57600080fd5b810190602081018135600160201b8111156105d757600080fd5b8201836020820111156105e957600080fd5b803590602001918460018302840111600160201b8311171561060a57600080fd5b50909250905061170e565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610651578181015183820152602001610639565b505050509050019250505060405180910390f35b61015d61185b565b6104246004803603604081101561068357600080fd5b810190602081018135600160201b81111561069d57600080fd5b8201836020820111156106af57600080fd5b803590602001918460018302840111600160201b831117156106d057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250611862915050565b6101dd6004803603606081101561072957600080fd5b810190602081018135600160201b81111561074357600080fd5b82018360208201111561075557600080fd5b803590602001918460018302840111600160201b8311171561077657600080fd5b9193509150803590602001356119ba565b6101dd6004803603602081101561079d57600080fd5b810190602081018135600160201b8111156107b757600080fd5b8201836020820111156107c957600080fd5b803590602001918460018302840111600160201b831117156107ea57600080fd5b509092509050611ae6565b6104556004803603602081101561080b57600080fd5b810190602081018135600160201b81111561082557600080fd5b82018360208201111561083757600080fd5b803590602001918460018302840111600160201b8311171561085857600080fd5b509092509050611ef1565b6101dd6004803603602081101561087957600080fd5b810190602081018135600160201b81111561089357600080fd5b8201836020820111156108a557600080fd5b803590602001918460018302840111600160201b831117156108c657600080fd5b50909250905061225a565b610424600480360360208110156108e757600080fd5b810190602081018135600160201b81111561090157600080fd5b82018360208201111561091357600080fd5b803590602001918460018302840111600160201b8311171561093457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612407945050505050565b60015460408051600160e41b62e32cf902815290516000926001600160a01b031691630e32cf90916004808301926020929190829003018186803b1580156109bc57600080fd5b505afa1580156109d0573d6000803e3d6000fd5b505050506040513d60208110156109e657600080fd5b50516001600160a01b03163314610a3b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b83838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610a7d92508391506124079050565b1515600114610ace5760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8260011480610add5750826002145b1515610b1d57604051600160e51b62461bcd028152600401808060200182810382526025815260200180613a646025913960400191505060405180910390fd5b6000610b5e86868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b9050600481815481101515610b6f57fe5b9060005260206000209060080201600601546001141515610bc457604051600160e51b62461bcd028152600401808060200182810382526027815260200180613a896027913960400191505060405180910390fd5b6000808560011415610bdb57506002905080610bec565b8560021415610bec57506004905060035b610c2d88888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250869250611862915050565b1515600114610c7057604051600160e51b62461bcd028152600401808060200182810382526027815260200180613ab06027913960400191505060405180910390fd5b8560011415610cbd57610cb888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061252292505050565b610cfc565b610cfc88888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506127e492505050565b979650505050505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610d5557600080fd5b505afa158015610d69573d6000803e3d6000fd5b505050506040513d6020811015610d7f57600080fd5b50516001600160a01b03163314610dd45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610e1692508391506124079050565b1515600114610e675760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b8160011415610eb457610eaf84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061299192505050565b610ef3565b610ef384848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612c4f92505050565b50505050565b60015460408051600160e41b62e32cf902815290516060926001600160a01b031691630e32cf90916004808301926020929190829003018186803b158015610f4057600080fd5b505afa158015610f54573d6000803e3d6000fd5b505050506040513d6020811015610f6a57600080fd5b50516001600160a01b03163314610fbf5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600461100084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b8154811061100a57fe5b6000918252602091829020600460089092020101805460408051601f60026000196101006001871615020190941693909304928301859004850281018501909152818152928301828280156110a05780601f10611075576101008083540402835291602001916110a0565b820191906000526020600020905b81548152906001019060200180831161108357829003601f168201915b505050505090505b92915050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156110fc57600080fd5b505afa158015611110573d6000803e3d6000fd5b505050506040513d602081101561112657600080fd5b50516001600160a01b0316331461117b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b8383838360405160200180858580828437600160f91b601702920191825250600101838380828437808301925050509450505050506040516020818303038152906040526111c881612407565b1561120d5760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b61128585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525060029250829150612d0e9050565b5050505050565b600060056000836040516020018082805190602001908083835b602083106112c55780518252601f1990920191602091820191016112a6565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014151561149057600061132383612495565b905060048181548110151561133457fe5b906000526020600020906008020160010154600214806113735750600480548290811061135d57fe5b9060005260206000209060080201600101546003145b1561148e57600061142c60048381548110151561138c57fe5b6000918252602091829020600460089092020101805460408051601f60026000196101006001871615020190941693909304928301859004850281018501909152818152928301828280156114225780601f106113f757610100808354040283529160200191611422565b820191906000526020600020905b81548152906001019060200180831161140557829003601f168201915b5050505050612495565b905060048181548110151561143d57fe5b9060005260206000209060080201600101546002148061147c5750600480548290811061146657fe5b9060005260206000209060080201600101546003145b1561148c57600192505050611494565b505b505b5060005b919050565b60608060606000806004868154811015156114b057fe5b90600052602060002090600802016000016004878154811015156114d057fe5b90600052602060002090600802016002016004888154811015156114f057fe5b906000526020600020906008020160040160048981548110151561151057fe5b90600052602060002090600802016006015460048a81548110151561153157fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156115d85780601f106115ad576101008083540402835291602001916115d8565b820191906000526020600020905b8154815290600101906020018083116115bb57829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a50899450925084019050828280156116665780601f1061163b57610100808354040283529160200191611666565b820191906000526020600020905b81548152906001019060200180831161164957829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156116f45780601f106116c9576101008083540402835291602001916116f4565b820191906000526020600020905b8154815290600101906020018083116116d757829003601f168201915b505050505092509450945094509450945091939590929450565b606061174f83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061240792505050565b15156001146117a05760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b60006117e184848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b90506004818154811015156117f257fe5b906000526020600020906008020160070180548060200260200160405190810160405280929190818152602001828054801561184d57602002820191906000526020600020905b815481526020019060010190808311611839575b505050505091505092915050565b6004545b90565b600060056000846040516020018082805190602001908083835b6020831061189b5780518252601f19909201916020918201910161187c565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014156118f5575060006110a8565b600061190084612495565b905060056000856040516020018082805190602001908083835b602083106119395780518252601f19909201916020918201910161191a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001208152602001908152602001600020546000141580156119b257508260048281548110151561199e57fe5b906000526020600020906008020160010154145b949350505050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611a0857600080fd5b505afa158015611a1c573d6000803e3d6000fd5b505050506040513d6020811015611a3257600080fd5b50516001600160a01b03163314611a875760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611adc6040518060200160405280600081525085858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506001925060029150612d0e9050565b6002556003555050565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611b3457600080fd5b505afa158015611b48573d6000803e3d6000fd5b505050506040513d6020811015611b5e57600080fd5b50516001600160a01b03163314611bb35760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611bf582828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250611862915050565b1515600114611c465760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b6000611c8783838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b90506002600482815481101515611c9a57fe5b9060005260206000209060080201600101819055507fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c600482815481101515611cdf57fe5b9060005260206000209060080201600001600483815481101515611cff57fe5b9060005260206000209060080201600201600484815481101515611d1f57fe5b9060005260206000209060080201600401600485815481101515611d3f57fe5b906000526020600020906008020160060154600260405180806020018060200180602001868152602001858152602001848103845289818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015611df05780601f10611dc557610100808354040283529160200191611df0565b820191906000526020600020905b815481529060010190602001808311611dd357829003601f168201915b5050848103835288546002600019610100600184161502019091160480825260209091019089908015611e645780601f10611e3957610100808354040283529160200191611e64565b820191906000526020600020905b815481529060010190602001808311611e4757829003601f168201915b5050848103825287546002600019610100600184161502019091160480825260209091019088908015611ed85780601f10611ead57610100808354040283529160200191611ed8565b820191906000526020600020905b815481529060010190602001808311611ebb57829003601f168201915b50509850505050505050505060405180910390a1505050565b6060806060600080611f3887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061240792505050565b1515611fa757868660008083838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525060408051602080820183528382528251908101909252918152949d509b50929950939750919550612250945050505050565b6000611fe888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061249592505050565b9050600481815481101515611ff957fe5b906000526020600020906008020160000160048281548110151561201957fe5b906000526020600020906008020160020160048381548110151561203957fe5b906000526020600020906008020160040160048481548110151561205957fe5b90600052602060002090600802016006015460048581548110151561207a57fe5b906000526020600020906008020160010154848054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156121215780601f106120f657610100808354040283529160200191612121565b820191906000526020600020905b81548152906001019060200180831161210457829003601f168201915b5050875460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959a50899450925084019050828280156121af5780601f10612184576101008083540402835291602001916121af565b820191906000526020600020905b81548152906001019060200180831161219257829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529599508894509250840190508282801561223d5780601f106122125761010080835404028352916020019161223d565b820191906000526020600020905b81548152906001019060200180831161222057829003601f168201915b5050505050925095509550955095509550505b9295509295909350565b600160009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156122a857600080fd5b505afa1580156122bc573d6000803e3d6000fd5b505050506040513d60208110156122d257600080fd5b50516001600160a01b031633146123275760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b81818080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061236992508391506124079050565b156123ae5760408051600160e51b62461bcd02815260206004820152600a6024820152600160b01b696f72672065786973747302604482015290519081900360640190fd5b6124026040518060200160405280600081525084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250829150612d0e9050565b505050565b600060056000836040516020018082805190602001908083835b602083106124405780518252601f199092019160209182019101612421565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054600014159050919050565b6000600160056000846040516020018082805190602001908083835b602083106124d05780518252601f1990920191602091820191016124b1565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b61252d816002611862565b151560011461257057604051600160e51b62461bcd028152600401808060200182810382526034815260200180613ad76034913960400191505060405180910390fd5b600061257b82612495565b9050600360048281548110151561258e57fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156125d357fe5b90600052602060002090600802016000016004838154811015156125f357fe5b906000526020600020906008020160020160048481548110151561261357fe5b906000526020600020906008020160040160048581548110151561263357fe5b9060005260206000209060080201600601546003604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156126e45780601f106126b9576101008083540402835291602001916126e4565b820191906000526020600020905b8154815290600101906020018083116126c757829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156127585780601f1061272d57610100808354040283529160200191612758565b820191906000526020600020905b81548152906001019060200180831161273b57829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156127cc5780601f106127a1576101008083540402835291602001916127cc565b820191906000526020600020905b8154815290600101906020018083116127af57829003601f168201915b50509850505050505050505060405180910390a15050565b6127ef816004611862565b15156001146128485760408051600160e51b62461bcd02815260206004820152601a60248201527f6f7267206e6f7420696e2073757370656e646564207374617465000000000000604482015290519081900360640190fd5b600061285382612495565b9050600560048281548110151561286657fe5b9060005260206000209060080201600101819055507f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156128ab57fe5b90600052602060002090600802016000016004838154811015156128cb57fe5b90600052602060002090600802016002016004848154811015156128eb57fe5b906000526020600020906008020160040160048581548110151561290b57fe5b9060005260206000209060080201600601546005604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156126e45780601f106126b9576101008083540402835291602001916126e4565b61299c816003611862565b15156001146129ed5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b60006129f882612495565b905060048082815481101515612a0a57fe5b9060005260206000209060080201600101819055507f73ccf8d6c8385bf5347269bd59712da33183c1a5e1702494bcdb87d0f4674d96600482815481101515612a4f57fe5b9060005260206000209060080201600001600483815481101515612a6f57fe5b9060005260206000209060080201600201600484815481101515612a8f57fe5b9060005260206000209060080201600401600485815481101515612aaf57fe5b600091825260209182902060066008909202010154604080516060810183905260808082528754600260001961010060018416150201909116049082018190529293909283929183019183019060a084019089908015612b505780601f10612b2557610100808354040283529160200191612b50565b820191906000526020600020905b815481529060010190602001808311612b3357829003601f168201915b5050848103835287546002600019610100600184161502019091160480825260209091019088908015612bc45780601f10612b9957610100808354040283529160200191612bc4565b820191906000526020600020905b815481529060010190602001808311612ba757829003601f168201915b5050848103825286546002600019610100600184161502019091160480825260209091019087908015612c385780601f10612c0d57610100808354040283529160200191612c38565b820191906000526020600020905b815481529060010190602001808311612c1b57829003601f168201915b505097505050505050505060405180910390a15050565b612c5a816005611862565b1515600114612cab5760408051600160e51b62461bcd0281526020600482015260126024820152600160701b716e6f7468696e6720746f20617070726f766502604482015290519081900360640190fd5b6000612cb682612495565b90506002600482815481101515612cc957fe5b9060005260206000209060080201600101819055507f882f030c609566cd82918a97d457fd48f9cfcefd11282e2654cde3f94579c15f600482815481101515612a4f57fe5b600080806001851415612d9057856040516020018082805190602001908083835b60208310612d4e5780518252601f199092019160209182019101612d2f565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001209150612ecf565b866040516020018082805190602001908083835b60208310612dc35780518252601f199092019160209182019101612da4565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120925086866040516020018083805190602001908083835b60208310612e345780518252601f199092019160209182019101612e15565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b60208310612e905780518252601f199092019160209182019101612e71565b6001836020036101000a038019825116818451168082178552505050505050905001925050506040516020818303038152906040528051906020012091505b600680546001908101918290556000848152600560205260408120929092556004805491612eff9190830161382a565b90508560011415612fc45785600482815481101515612f1a57fe5b9060005260206000209060080201600601819055506000600482815481101515612f4057fe5b90600052602060002090600802016005018190555086600482815481101515612f6557fe5b90600052602060002090600802016003019080519060200190612f89929190613856565b5086600482815481101515612f9a57fe5b90600052602060002090600802016004019080519060200190612fbe929190613856565b50613308565b600084815260056020526040902054600354600480546000199093019450909184908110612fee57fe5b6000918252602090912060076008909202010154106130575760408051600160e51b62461bcd02815260206004820152601660248201527f62726561647468206c6576656c20657863656564656400000000000000000000604482015290519081900360640190fd5b600254600480548490811061306857fe5b9060005260206000209060080201600601541015156130d15760408051600160e51b62461bcd02815260206004820152601460248201527f6465707468206c6576656c206578636565646564000000000000000000000000604482015290519081900360640190fd5b60048054839081106130df57fe5b90600052602060002090600802016006015460010160048281548110151561310357fe5b9060005260206000209060080201600601819055508160048281548110151561312857fe5b6000918252602090912060056008909202010155600480548390811061314a57fe5b906000526020600020906008020160040160048281548110151561316a57fe5b9060005260206000209060080201600401908054600181600116156101000203166002900461319a9291906138d4565b5060006004838154811015156131ac57fe5b906000526020600020906008020160070180548091906001016131cf9190613949565b9050816004848154811015156131e157fe5b9060005260206000209060080201600701828154811015156131ff57fe5b906000526020600020018190555088886040516020018083805190602001908083835b602083106132415780518252601f199092019160209182019101613222565b6001836020036101000a03801982511681845116808217855250505050505090500180600160f91b60170281525060010182805190602001908083835b6020831061329d5780518252601f19909201916020918201910161327e565b6001836020036101000a038019825116818451168082178552505050505050905001925050506040516020818303038152906040526004838154811015156132e157fe5b90600052602060002090600802016003019080519060200190613305929190613856565b50505b8660048281548110151561331857fe5b9060005260206000209060080201600001908051906020019061333c929190613856565b508760048281548110151561334d57fe5b90600052602060002090600802016002019080519060200190613371929190613856565b508460048281548110151561338257fe5b90600052602060002090600802016001018190555084600114156135e2577f0e8b7be64e0c730234ba2cd252b227fb481d7a247ba806d1941144c535bf054b6004828154811015156133d057fe5b90600052602060002090600802016000016004838154811015156133f057fe5b906000526020600020906008020160020160048481548110151561341057fe5b906000526020600020906008020160040160048581548110151561343057fe5b9060005260206000209060080201600601546001604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156134e15780601f106134b6576101008083540402835291602001916134e1565b820191906000526020600020905b8154815290600101906020018083116134c457829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156135555780601f1061352a57610100808354040283529160200191613555565b820191906000526020600020905b81548152906001019060200180831161353857829003601f168201915b50508481038252875460026000196101006001841615020190911604808252602090910190889080156135c95780601f1061359e576101008083540402835291602001916135c9565b820191906000526020600020905b8154815290600101906020018083116135ac57829003601f168201915b50509850505050505050505060405180910390a1613820565b7fd705723a50859c9cc1d3953e10b8b9478820e7a62927ad3215897ed87b20591c60048281548110151561361257fe5b906000526020600020906008020160000160048381548110151561363257fe5b906000526020600020906008020160020160048481548110151561365257fe5b906000526020600020906008020160040160048581548110151561367257fe5b9060005260206000209060080201600601546002604051808060200180602001806020018681526020018581526020018481038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156137235780601f106136f857610100808354040283529160200191613723565b820191906000526020600020905b81548152906001019060200180831161370657829003601f168201915b50508481038352885460026000196101006001841615020190911604808252602090910190899080156137975780601f1061376c57610100808354040283529160200191613797565b820191906000526020600020905b81548152906001019060200180831161377a57829003601f168201915b505084810382528754600260001961010060018416150201909116048082526020909101908890801561380b5780601f106137e05761010080835404028352916020019161380b565b820191906000526020600020905b8154815290600101906020018083116137ee57829003601f168201915b50509850505050505050505060405180910390a15b5050505050505050565b81548183558181111561240257600802816008028360005260206000209182019101612402919061396d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061389757805160ff19168380011785556138c4565b828001600101855582156138c4579182015b828111156138c45782518255916020019190600101906138a9565b506138d09291506139e4565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061390d57805485556138c4565b828001600101855582156138c457600052602060002091601f016020900482015b828111156138c457825482559160010191906001019061392e565b815481835581811115612402576000838152602090206124029181019083016139e4565b61185f91905b808211156138d057600061398782826139fe565b600182016000905560028201600061399f91906139fe565b6139ad6003830160006139fe565b6139bb6004830160006139fe565b600582016000905560068201600090556007820160006139db9190613a45565b50600801613973565b61185f91905b808211156138d057600081556001016139ea565b50805460018160011615610100020316600290046000825580601f10613a245750613a42565b601f016020900490600052602060002090810190613a4291906139e4565b50565b5080546000825590600052602060002090810190613a4291906139e456fe696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f7765646e6f742061206d6173746572206f72672e206f7065726174696f6e206e6f7420616c6c6f7765646f72672073746174757320646f6573206e6f7420616c6c6f7720746865206f7065726174696f6e6f7267206e6f7420696e20617070726f766564207374617475732e206f7065726174696f6e2063616e6e6f7420626520646f6e65a165627a7a72305820e20472358f6cc444b5476d1b471677de46e77b7421a1bcae4549f346a29e10870029 \ No newline at end of file diff --git a/permission/v2/contract/gen/PermissionsImplementation.abi b/permission/v2/contract/gen/PermissionsImplementation.abi new file mode 100644 index 0000000000..c0045990fc --- /dev/null +++ b/permission/v2/contract/gen/PermissionsImplementation.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"updateAccountStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_access","type":"uint256"},{"name":"_voter","type":"bool"},{"name":"_admin","type":"bool"},{"name":"_caller","type":"address"}],"name":"addNewRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminOrg","type":"string"},{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"}],"name":"setPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"startBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"updateOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_roleId","type":"string"},{"name":"_caller","type":"address"}],"name":"assignAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"}],"name":"connectionAllowed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"approveBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"}],"name":"addAdminAccount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_caller","type":"address"}],"name":"removeRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_pOrgId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_caller","type":"address"}],"name":"addSubOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"validateAccount","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"}],"name":"addAdminNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"approveAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"},{"name":"_caller","type":"address"}],"name":"assignAccountRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_target","type":"address"},{"name":"_value","type":"uint256"},{"name":"_gasPrice","type":"uint256"},{"name":"_gasLimit","type":"uint256"},{"name":"_payload","type":"bytes"}],"name":"transactionAllowed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"isOrgAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_caller","type":"address"}],"name":"approveBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_breadth","type":"uint256"},{"name":"_depth","type":"uint256"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"approveOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_action","type":"uint256"},{"name":"_caller","type":"address"}],"name":"updateNodeStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPolicyDetails","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"isNetworkAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_caller","type":"address"}],"name":"startBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"addOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_caller","type":"address"}],"name":"addNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getPendingOp","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminOrg","type":"string"},{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"},{"name":"_networkBootStatus","type":"bool"}],"name":"setMigrationPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_account","type":"address"},{"name":"_caller","type":"address"}],"name":"approveOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"},{"name":"_orgManager","type":"address"},{"name":"_rolesManager","type":"address"},{"name":"_accountManager","type":"address"},{"name":"_voterManager","type":"address"},{"name":"_nodeManager","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_networkBootStatus","type":"bool"}],"name":"PermissionsInitialized","type":"event"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/PermissionsImplementation.bin b/permission/v2/contract/gen/PermissionsImplementation.bin new file mode 100644 index 0000000000..a03b54bfc2 --- /dev/null +++ b/permission/v2/contract/gen/PermissionsImplementation.bin @@ -0,0 +1 @@ +60806040526003600955600a805460ff191690553480156200002057600080fd5b5060405160c08062008587833981018060405260c08110156200004257600080fd5b508051602082015160408301516060840151608085015160a090950151600580546001600160a01b039687166001600160a01b03199182161790915560048054958716958216959095179094556001805493861693851693909317909255600080549185169184169190911790556002805494841694831694909417909355600380549290931691161790556184a980620000de6000396000f3fe608060405234801561001057600080fd5b50600436106101e55760003560e01c8063888430411161010f578063cc9ba6fa116100a2578063ecad01d511610071578063ecad01d5146117db578063f346a3a7146119a5578063f5ad584a14611b11578063f75f0a0614611c21576101e5565b8063cc9ba6fa146112c4578063d1aa0c2014611418578063d621d9571461143e578063e91b0e1914611608576101e5565b8063a042bf40116100de578063a042bf4014610e8b578063a5843f0814611055578063b554656414611078578063b9b7fe6c146110f5576101e5565b80638884304114610b7a5780638baa819114610bf9578063936421d514610d3d5780639bd3810114610dd7576101e5565b806345a59e5b116101875780635ca5adbe116101565780635ca5adbe146107b957806368a61273146108805780636b568d76146109fc5780638683c7fe14610ab0576101e5565b806345a59e5b1461064a5780634b20f45f1461070c5780634cbfa82e1461078b5780634fe57e7a14610793576101e5565b80631c249912116101c35780631c2499121461045e5780633cf5f33b146104dd578063404bf3eb1461055a57806344478e791461062e576101e5565b806304e81f1e146101ea5780631b04c276146102735780631b61022014610350575b600080fd5b6102716004803603608081101561020057600080fd5b810190602081018135600160201b81111561021a57600080fd5b82018360208201111561022c57600080fd5b803590602001918460018302840111600160201b8311171561024d57600080fd5b91935091506001600160a01b03813581169160208101359160409091013516611df4565b005b610271600480360360c081101561028957600080fd5b810190602081018135600160201b8111156102a357600080fd5b8201836020820111156102b557600080fd5b803590602001918460018302840111600160201b831117156102d657600080fd5b919390929091602081019035600160201b8111156102f357600080fd5b82018360208201111561030557600080fd5b803590602001918460018302840111600160201b8311171561032657600080fd5b919350915080359060208101351515906040810135151590606001356001600160a01b0316612047565b6102716004803603606081101561036657600080fd5b810190602081018135600160201b81111561038057600080fd5b82018360208201111561039257600080fd5b803590602001918460018302840111600160201b831117156103b357600080fd5b919390929091602081019035600160201b8111156103d057600080fd5b8201836020820111156103e257600080fd5b803590602001918460018302840111600160201b8311171561040357600080fd5b919390929091602081019035600160201b81111561042057600080fd5b82018360208201111561043257600080fd5b803590602001918460018302840111600160201b8311171561045357600080fd5b509092509050612309565b6102716004803603606081101561047457600080fd5b810190602081018135600160201b81111561048e57600080fd5b8201836020820111156104a057600080fd5b803590602001918460018302840111600160201b831117156104c157600080fd5b91935091506001600160a01b0381358116916020013516612448565b610271600480360360608110156104f357600080fd5b810190602081018135600160201b81111561050d57600080fd5b82018360208201111561051f57600080fd5b803590602001918460018302840111600160201b8311171561054057600080fd5b9193509150803590602001356001600160a01b0316612747565b6102716004803603608081101561057057600080fd5b810190602081018135600160201b81111561058a57600080fd5b82018360208201111561059c57600080fd5b803590602001918460018302840111600160201b831117156105bd57600080fd5b919390926001600160a01b0383351692604081019060200135600160201b8111156105e757600080fd5b8201836020820111156105f957600080fd5b803590602001918460018302840111600160201b8311171561061a57600080fd5b9193509150356001600160a01b0316612a4f565b610636612e0e565b604080519115158252519081900360200190f35b6106366004803603606081101561066057600080fd5b810190602081018135600160201b81111561067a57600080fd5b82018360208201111561068c57600080fd5b803590602001918460018302840111600160201b831117156106ad57600080fd5b919390929091602081019035600160201b8111156106ca57600080fd5b8201836020820111156106dc57600080fd5b803590602001918460018302840111600160201b831117156106fd57600080fd5b91935091503561ffff16612f6c565b6102716004803603606081101561072257600080fd5b810190602081018135600160201b81111561073c57600080fd5b82018360208201111561074e57600080fd5b803590602001918460018302840111600160201b8311171561076f57600080fd5b91935091506001600160a01b0381358116916020013516613070565b6106366132b9565b610271600480360360208110156107a957600080fd5b50356001600160a01b03166132c3565b610271600480360360608110156107cf57600080fd5b810190602081018135600160201b8111156107e957600080fd5b8201836020820111156107fb57600080fd5b803590602001918460018302840111600160201b8311171561081c57600080fd5b919390929091602081019035600160201b81111561083957600080fd5b82018360208201111561084b57600080fd5b803590602001918460018302840111600160201b8311171561086c57600080fd5b9193509150356001600160a01b03166135e4565b610271600480360360e081101561089657600080fd5b810190602081018135600160201b8111156108b057600080fd5b8201836020820111156108c257600080fd5b803590602001918460018302840111600160201b831117156108e357600080fd5b919390929091602081019035600160201b81111561090057600080fd5b82018360208201111561091257600080fd5b803590602001918460018302840111600160201b8311171561093357600080fd5b919390929091602081019035600160201b81111561095057600080fd5b82018360208201111561096257600080fd5b803590602001918460018302840111600160201b8311171561098357600080fd5b919390929091602081019035600160201b8111156109a057600080fd5b8201836020820111156109b257600080fd5b803590602001918460018302840111600160201b831117156109d357600080fd5b9193509150803561ffff90811691602081013590911690604001356001600160a01b0316613aa6565b61063660048036036040811015610a1257600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610a3c57600080fd5b820183602082011115610a4e57600080fd5b803590602001918460018302840111600160201b83111715610a6f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613ef1945050505050565b61027160048036036080811015610ac657600080fd5b810190602081018135600160201b811115610ae057600080fd5b820183602082011115610af257600080fd5b803590602001918460018302840111600160201b83111715610b1357600080fd5b919390929091602081019035600160201b811115610b3057600080fd5b820183602082011115610b4257600080fd5b803590602001918460018302840111600160201b83111715610b6357600080fd5b919350915061ffff81358116916020013516613fdf565b61027160048036036060811015610b9057600080fd5b810190602081018135600160201b811115610baa57600080fd5b820183602082011115610bbc57600080fd5b803590602001918460018302840111600160201b83111715610bdd57600080fd5b91935091506001600160a01b0381358116916020013516614228565b61027160048036036080811015610c0f57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610c3957600080fd5b820183602082011115610c4b57600080fd5b803590602001918460018302840111600160201b83111715610c6c57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610cbe57600080fd5b820183602082011115610cd057600080fd5b803590602001918460018302840111600160201b83111715610cf157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b0316915061467d9050565b610636600480360360c0811015610d5357600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b811115610d9957600080fd5b820183602082011115610dab57600080fd5b803590602001918460018302840111600160201b83111715610dcc57600080fd5b509092509050614b8b565b61063660048036036040811015610ded57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610e1757600080fd5b820183602082011115610e2957600080fd5b803590602001918460018302840111600160201b83111715610e4a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550615059945050505050565b610271600480360360c0811015610ea157600080fd5b810190602081018135600160201b811115610ebb57600080fd5b820183602082011115610ecd57600080fd5b803590602001918460018302840111600160201b83111715610eee57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610f4057600080fd5b820183602082011115610f5257600080fd5b803590602001918460018302840111600160201b83111715610f7357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610fc557600080fd5b820183602082011115610fd757600080fd5b803590602001918460018302840111600160201b83111715610ff857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b03169050615411565b6102716004803603604081101561106b57600080fd5b508035906020013561577d565b6102716004803603606081101561108e57600080fd5b810190602081018135600160201b8111156110a857600080fd5b8201836020820111156110ba57600080fd5b803590602001918460018302840111600160201b831117156110db57600080fd5b9193509150803590602001356001600160a01b0316615c4c565b610271600480360360e081101561110b57600080fd5b810190602081018135600160201b81111561112557600080fd5b82018360208201111561113757600080fd5b803590602001918460018302840111600160201b8311171561115857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156111aa57600080fd5b8201836020820111156111bc57600080fd5b803590602001918460018302840111600160201b831117156111dd57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561122f57600080fd5b82018360208201111561124157600080fd5b803590602001918460018302840111600160201b8311171561126257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff90811693506020830135169160408101359150606001356001600160a01b0316615fa5565b6112cc61619f565b604080518215156060820152608080825286519082015285519091829160208084019284019160a08501918a019080838360005b83811015611318578181015183820152602001611300565b50505050905090810190601f1680156113455780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b83811015611378578181015183820152602001611360565b50505050905090810190601f1680156113a55780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b838110156113d85781810151838201526020016113c0565b50505050905090810190601f1680156114055780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b6106366004803603602081101561142e57600080fd5b50356001600160a01b0316616372565b610271600480360360c081101561145457600080fd5b810190602081018135600160201b81111561146e57600080fd5b82018360208201111561148057600080fd5b803590602001918460018302840111600160201b831117156114a157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156114f357600080fd5b82018360208201111561150557600080fd5b803590602001918460018302840111600160201b8311171561152657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561157857600080fd5b82018360208201111561158a57600080fd5b803590602001918460018302840111600160201b831117156115ab57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b03169050616576565b610271600480360360e081101561161e57600080fd5b810190602081018135600160201b81111561163857600080fd5b82018360208201111561164a57600080fd5b803590602001918460018302840111600160201b8311171561166b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156116bd57600080fd5b8201836020820111156116cf57600080fd5b803590602001918460018302840111600160201b831117156116f057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561174257600080fd5b82018360208201111561175457600080fd5b803590602001918460018302840111600160201b8311171561177557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff833581169450602084013516926001600160a01b036040820135811693506060909101351690506169fb565b610271600480360360c08110156117f157600080fd5b810190602081018135600160201b81111561180b57600080fd5b82018360208201111561181d57600080fd5b803590602001918460018302840111600160201b8311171561183e57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561189057600080fd5b8201836020820111156118a257600080fd5b803590602001918460018302840111600160201b831117156118c357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561191557600080fd5b82018360208201111561192757600080fd5b803590602001918460018302840111600160201b8311171561194857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b0316905061713d565b611a13600480360360208110156119bb57600080fd5b810190602081018135600160201b8111156119d557600080fd5b8201836020820111156119e757600080fd5b803590602001918460018302840111600160201b83111715611a0857600080fd5b5090925090506173e7565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015611a72578181015183820152602001611a5a565b50505050905090810190601f168015611a9f5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015611ad2578181015183820152602001611aba565b50505050905090810190601f168015611aff5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61027160048036036080811015611b2757600080fd5b810190602081018135600160201b811115611b4157600080fd5b820183602082011115611b5357600080fd5b803590602001918460018302840111600160201b83111715611b7457600080fd5b919390929091602081019035600160201b811115611b9157600080fd5b820183602082011115611ba357600080fd5b803590602001918460018302840111600160201b83111715611bc457600080fd5b919390929091602081019035600160201b811115611be157600080fd5b820183602082011115611bf357600080fd5b803590602001918460018302840111600160201b83111715611c1457600080fd5b919350915035151561754f565b610271600480360360e0811015611c3757600080fd5b810190602081018135600160201b811115611c5157600080fd5b820183602082011115611c6357600080fd5b803590602001918460018302840111600160201b83111715611c8457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611cd657600080fd5b820183602082011115611ce857600080fd5b803590602001918460018302840111600160201b83111715611d0957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611d5b57600080fd5b820183602082011115611d6d57600080fd5b803590602001918460018302840111600160201b83111715611d8e57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff833581169450602084013516926001600160a01b0360408201358116935060609091013516905061763e565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015611e4257600080fd5b505afa158015611e56573d6000803e3d6000fd5b505050506040513d6020811015611e6c57600080fd5b50516001600160a01b03163314611eb757604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8085858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611efb9250849150839050615059565b1515600114611f3e57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b8360011480611f4d5750836002145b80611f585750836003145b1515611f9857604051600160e51b62461bcd0281526004018080602001828103825260258152602001806183e96025913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03878116602483015260448201879052606060048301908152606483018a90529216916384b7a84a918a918a918a918a918190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561202657600080fd5b505af115801561203a573d6000803e3d6000fd5b5050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561209557600080fd5b505afa1580156120a9573d6000803e3d6000fd5b505050506040513d60208110156120bf57600080fd5b50516001600160a01b0316331461210a57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b85858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061214c9250839150617d129050565b15156001146121935760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b8187878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506121d79250849150839050615059565b151560011461221a57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b600154604051600160e01b637b713579028152604481018990528715156064820152861515608482015260a06004820190815260a482018d90526001600160a01b0390921691637b713579918e918e918e918e918e918e918e91908190602481019060c4018a8a80828437600083820152601f01601f191690910184810383528881526020019050888880828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b1580156122e457600080fd5b505af11580156122f8573d6000803e3d6000fd5b505050505050505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561235757600080fd5b505afa15801561236b573d6000803e3d6000fd5b505050506040513d602081101561238157600080fd5b50516001600160a01b031633146123cc57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff16156124185760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b6124246006888861831a565b506124316007868661831a565b5061243e6008848461831a565b5050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561249657600080fd5b505afa1580156124aa573d6000803e3d6000fd5b505050506040513d60208110156124c057600080fd5b50516001600160a01b0316331461250b57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061251581616372565b151560011461255857604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b600054604051600160e11b63425bd4250281526001600160a01b03858116602483015260046044830181905260608382019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156125ea57600080fd5b505af11580156125fe573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b03888116606483015260066084830181905260a060048401908152815460001960018216156101000201169590950460a4840181905291909316955063e98ac22d945091928a928a928a928692909182916024820191604481019160c490910190869080156126cb5780601f106126a0576101008083540402835291602001916126cb565b820191906000526020600020905b8154815290600101906020018083116126ae57829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b15801561272857600080fd5b505af115801561273c573d6000803e3d6000fd5b505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561279557600080fd5b505afa1580156127a9573d6000803e3d6000fd5b505050506040513d60208110156127bf57600080fd5b50516001600160a01b0316331461280a57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061281481616372565b151560011461285757604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b6004805460408051600160e01b630cc2749302815260248101879052928301908152604483018790526000926001600160a01b0390921691630cc27493918991899189918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b1580156128dd57600080fd5b505af11580156128f1573d6000803e3d6000fd5b505050506040513d602081101561290757600080fd5b505160028054604051600160e01b63e98ac22d0281526000606482018190526084820185905260a0600483019081526006805460001960018216156101000201169590950460a484018190529596506001600160a01b039093169463e98ac22d94938c938c939289929182916024820191604481019160c4909101908a9080156129d25780601f106129a7576101008083540402835291602001916129d2565b820191906000526020600020905b8154815290600101906020018083116129b557829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015612a2f57600080fd5b505af1158015612a43573d6000803e3d6000fd5b50505050505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015612a9d57600080fd5b505afa158015612ab1573d6000803e3d6000fd5b505050506040513d6020811015612ac757600080fd5b50516001600160a01b03163314612b1257604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612b549250839150617e019050565b1515600114612ba55760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b81612baf81616372565b1515600114612bf257604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b600054604051600160e01b63e3483a9d0281526001600160a01b0388811660048301908152600160648401819052608060248501908152608485018d9052929094169363e3483a9d938b938e938e938d938d9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b158015612cb557600080fd5b505af1158015612cc9573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d0281526001600160a01b038b8116606483015260046084830181905260a08382019081526006805460001960018216156101000201169690960460a4850181905292909416965063e98ac22d95508e938e938e9382916024810191604482019160c401908a908015612d8f5780601f10612d6457610100808354040283529160200191612d8f565b820191906000526020600020905b815481529060010190602001808311612d7257829003601f168201915b50508481038352878152602001888880828437600081840152601f19601f82011690508083019250505084810382526000815260200160200198505050505050505050600060405180830381600087803b158015612dec57600080fd5b505af1158015612e00573d6000803e3d6000fd5b505050505050505050505050565b60055460408051600160e21b63395c945702815290516000926001600160a01b03169163e572515c916004808301926020929190829003018186803b158015612e5657600080fd5b505afa158015612e6a573d6000803e3d6000fd5b505050506040513d6020811015612e8057600080fd5b50516001600160a01b03163314612ecb57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff1615612f175760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b600a805460ff1916600117908190556040805160ff9290921615158252517f04f651be6fb8fc575d94591e56e9f6e66e33ef23e949765918b3bdae50c617cf9181900360200190a1600a5460ff1691505b5090565b600a5460009060ff161515612f8357506001613067565b600354604051600160e01b6345a59e5b02815261ffff84166044820152606060048201908152606482018890526001600160a01b03909216916345a59e5b91899189918991899189919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505097505050505050505060206040518083038186803b15801561303857600080fd5b505afa15801561304c573d6000803e3d6000fd5b505050506040513d602081101561306257600080fd5b505190505b95945050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156130be57600080fd5b505afa1580156130d2573d6000803e3d6000fd5b505050506040513d60208110156130e857600080fd5b50516001600160a01b0316331461313357604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061313d81616372565b151560011461318057604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261321a939092909183018282801561320d5780601f106131e25761010080835404028352916020019161320d565b820191906000526020600020905b8154815290600101906020018083116131f057829003601f168201915b5050505050836006617ec6565b156132b257600054604051600160e11b63425bd4250281526001600160a01b0385811660248301526005604483018190526060600484019081526064840189905291909316926384b7a84a928992899289929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561272857600080fd5b5050505050565b600a5460ff165b90565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561331157600080fd5b505afa158015613325573d6000803e3d6000fd5b505050506040513d602081101561333b57600080fd5b50516001600160a01b0316331461338657604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff16156133d25760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261346c939092909183018282801561345f5780601f106134345761010080835404028352916020019161345f565b820191906000526020600020905b81548152906001019060200180831161344257829003601f168201915b5050505050836001617fc0565b600054604051600160e01b63e3483a9d0281526001600160a01b038481166004830190815260026064840181905260806024850190815260068054600019600182161561010002011683900460848701819052949096169563e3483a9d95899591946007949390929091604481019160a490910190879080156135305780601f1061350557610100808354040283529160200191613530565b820191906000526020600020905b81548152906001019060200180831161351357829003601f168201915b50508381038252855460026000196101006001841615020190911604808252602090910190869080156135a45780601f10613579576101008083540402835291602001916135a4565b820191906000526020600020905b81548152906001019060200180831161358757829003601f168201915b50509650505050505050600060405180830381600087803b1580156135c857600080fd5b505af11580156135dc573d6000803e3d6000fd5b505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561363257600080fd5b505afa158015613646573d6000803e3d6000fd5b505050506040513d602081101561365c57600080fd5b50516001600160a01b031633146136a757604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506136e99250839150617d129050565b15156001146137305760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b8184848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506137749250849150839050615059565b15156001146137b757604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b604080516020808201908152600780546002600019610100600184161502019091160493830184905292909182916060909101908490801561383a5780601f1061380f5761010080835404028352916020019161383a565b820191906000526020600020905b81548152906001019060200180831161381d57829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405160208183030381529060405280519060200120141580156139a7575060408051602080820190815260088054600260001961010060018416150201909116049383018490529290918291606090910190849080156139355780601f1061390a57610100808354040283529160200191613935565b820191906000526020600020905b81548152906001019060200180831161391857829003601f168201915b50509250505060405160208183030381529060405280519060200120888860405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012014155b15156139fd5760408051600160e51b62461bcd02815260206004820152601d60248201527f61646d696e20726f6c65732063616e6e6f742062652072656d6f766564000000604482015290519081900360640190fd5b60015460408051600160e11b63531a180902815260048101918252604481018a90526001600160a01b039092169163a6343012918b918b918b918b919081906024810190606401878780828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612dec57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015613af457600080fd5b505afa158015613b08573d6000803e3d6000fd5b505050506040513d6020811015613b1e57600080fd5b50516001600160a01b03163314613b6957604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8a8a8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613bab9250839150617e019050565b1515600114613bfc5760408051600160e51b62461bcd0281526020600482015260126024820152600160721b711bdc99c8191bd95cc81b9bdd08195e1a5cdd02604482015290519081900360640190fd5b818c8c8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613c409250849150839050615059565b1515600114613c8357604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b600460009054906101000a90046001600160a01b03166001600160a01b0316631f9534808f8f8f8f6040518563ffffffff1660e01b81526004018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015613d3657600080fd5b505af1158015613d4a573d6000803e3d6000fd5b5050505060608e8e8e8e60405160200180858580828437600160f91b6017029201918252506001018383808284376040805191909301818103601f19018252909252509550508d1593506122f89250505057600354604051600160e01b634c57331102815261ffff808a1660448301528816606482015260a06004820190815260a482018d90526001600160a01b0390921691634c573311918e918e918e918e918e918e918a919081906024810190608481019060c4018b8b80828437600083820152601f01601f1916909101858103845289815260200190508989808284376000838201819052601f909101601f191690920186810384528751815287516020918201939189019250908190849084905b83811015613e74578181015183820152602001613e5c565b50505050905090810190601f168015613ea15780820380516001836020036101000a031916815260200191505b509a5050505050505050505050600060405180830381600087803b158015613ec857600080fd5b505af1158015613edc573d6000803e3d6000fd5b50505050505050505050505050505050505050565b6000805460408051600160e11b6335ab46bb0281526001600160a01b0386811660048301908152602483019384528651604484015286519190941693636b568d76938893889360649091019060208501908083838c5b83811015613f5f578181015183820152602001613f47565b50505050905090810190601f168015613f8c5780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015613faa57600080fd5b505afa158015613fbe573d6000803e3d6000fd5b505050506040513d6020811015613fd457600080fd5b505190505b92915050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561402d57600080fd5b505afa158015614041573d6000803e3d6000fd5b505050506040513d602081101561405757600080fd5b50516001600160a01b031633146140a257604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff16156140ee5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b600354604051600160e01b634530abe102815261ffff80861660448301528416606482015260a06004820190815260a482018990526001600160a01b0390921691634530abe1918a918a918a918a918a918a916006919081906024810190608481019060c4018b8b80828437600083820152601f01601f191690910185810384528981526020019050898980828437600083820152601f01601f1916909101858103835286546002600019610100600184161502019091160480825260209091019150869080156142005780601f106141d557610100808354040283529160200191614200565b820191906000526020600020905b8154815290600101906020018083116141e357829003601f168201915b50509a5050505050505050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561427657600080fd5b505afa15801561428a573d6000803e3d6000fd5b505050506040513d60208110156142a057600080fd5b50516001600160a01b031633146142eb57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b806142f581616372565b151560011461433857604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526143d293909290918301828280156143c55780601f1061439a576101008083540402835291602001916143c5565b820191906000526020600020905b8154815290600101906020018083116143a857829003601f168201915b5050505050836004617ec6565b156132b25760008054604051600160e01b631d09dc930281526020600482019081526024820188905283926001600160a01b031691631d09dc93918a918a91908190604401848480828437600081840152601f19601f82011690508083019250505093505050506040805180830381600087803b15801561445257600080fd5b505af1158015614466573d6000803e3d6000fd5b505050506040513d604081101561447c57600080fd5b5080516020909101519092509050811561452a5760068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261452a939092909183018282801561451d5780601f106144f25761010080835404028352916020019161451d565b820191906000526020600020905b81548152906001019060200180831161450057829003601f168201915b5050505050826000617fc0565b6000805460408051600160e01b63c214e5e50281526001600160a01b03898116602483015260048201928352604482018b90529092169163c214e5e5918b918b918b918190606401858580828437600081840152601f19601f820116905080830192505050945050505050602060405180830381600087803b1580156145af57600080fd5b505af11580156145c3573d6000803e3d6000fd5b505050506040513d60208110156145d957600080fd5b50519050801561243e5760068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261243e93909290918301828280156146705780601f1061464557610100808354040283529160200191614670565b820191906000526020600020905b81548152906001019060200180831161465357829003601f168201915b5050505050876001617fc0565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156146cb57600080fd5b505afa1580156146df573d6000803e3d6000fd5b505050506040513d60208110156146f557600080fd5b50516001600160a01b0316331461474057604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b808361474c8282615059565b151560011461478f57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b8461479981617d12565b15156001146147e05760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b6147ea8787613ef1565b15156001146148435760408051600160e51b62461bcd02815260206004820152601d60248201527f6f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b61484d8587618156565b15156001146148a65760408051600160e51b62461bcd02815260206004820152601460248201527f726f6c6520646f6573206e6f7420657869737473000000000000000000000000604482015290519081900360640190fd5b6001546000906001600160a01b031663be322e5487896148c581618171565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b83811015614919578181015183820152602001614901565b50505050905090810190601f1680156149465780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b83811015614979578181015183820152602001614961565b50505050905090810190601f1680156149a65780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156149d95781810151838201526020016149c1565b50505050905090810190601f168015614a065780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b158015614a2757600080fd5b505afa158015614a3b573d6000803e3d6000fd5b505050506040513d6020811015614a5157600080fd5b505160008054604051600160e21b63050e95810281526001600160a01b038c81166004830190815285151560648401526080602484019081528d5160848501528d51969750919093169463143a5604948e948e948e948a9492939092604483019260a401916020890191908190849084905b83811015614adb578181015183820152602001614ac3565b50505050905090810190601f168015614b085780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838360005b83811015614b3b578181015183820152602001614b23565b50505050905090810190601f168015614b685780820380516001836020036101000a031916815260200191505b509650505050505050600060405180830381600087803b158015612dec57600080fd5b600a5460009060ff161515614ba25750600161504e565b60005460408051600160e11b637ea7d02d0281526001600160a01b038b811660048301529151919092169163fd4fa05a916024808301926020929190829003018186803b158015614bf257600080fd5b505afa158015614c06573d6000803e3d6000fd5b505050506040513d6020811015614c1c57600080fd5b50516002141561504a576000805460408051600160e01b636acee5fd0281526001600160a01b038c81166004830152915160609485949390931692636acee5fd9260248082019391829003018186803b158015614c7857600080fd5b505afa158015614c8c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015614cb557600080fd5b810190808051600160201b811115614ccc57600080fd5b82016020810184811115614cdf57600080fd5b8151600160201b811182820187101715614cf857600080fd5b50509291906020018051600160201b811115614d1357600080fd5b82016020810184811115614d2657600080fd5b8151600160201b811182820187101715614d3f57600080fd5b5050929190505050915091506060614d5683618171565b60048054604051600160e01b633fd62ae702815260209281018381528751602483015287519495506001600160a01b0390921693633fd62ae793889392839260449091019185019080838360005b83811015614dbc578181015183820152602001614da4565b50505050905090810190601f168015614de95780820380516001836020036101000a031916815260200191505b509250505060206040518083038186803b158015614e0657600080fd5b505afa158015614e1a573d6000803e3d6000fd5b505050506040513d6020811015614e3057600080fd5b50511561504657614e408b616372565b80614e505750614e508b84615059565b15614e61576001935050505061504e565b60016001600160a01b038b161515614e7b57506002614e85565b8515614e85575060035b600154604051600160e11b6368fbbc33028152606481018390526080600482019081528551608483015285516001600160a01b039093169263d1f778669287928992889288929182916024810191604482019160a4019060208a019080838360005b83811015614eff578181015183820152602001614ee7565b50505050905090810190601f168015614f2c5780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b83811015614f5f578181015183820152602001614f47565b50505050905090810190601f168015614f8c5780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b83811015614fbf578181015183820152602001614fa7565b50505050905090810190601f168015614fec5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038186803b15801561500e57600080fd5b505afa158015615022573d6000803e3d6000fd5b505050506040513d602081101561503857600080fd5b5051945061504e9350505050565b5050505b5060005b979650505050505050565b600080546001600160a01b031663e8b42bf4848461507681618171565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b031681526020018060200180602001838103835285818151815260200191508051906020019080838360005b838110156150de5781810151838201526020016150c6565b50505050905090810190601f16801561510b5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561513e578181015183820152602001615126565b50505050905090810190601f16801561516b5780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038186803b15801561518b57600080fd5b505afa15801561519f573d6000803e3d6000fd5b505050506040513d60208110156151b557600080fd5b5051156151c457506001613fd9565b6001546000805460408051600160e01b6381d66b230281526001600160a01b03888116600483015291519482169463be322e549493909216926381d66b2392602480840193829003018186803b15801561521d57600080fd5b505afa158015615231573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561525a57600080fd5b810190808051600160201b81111561527157600080fd5b8201602081018481111561528457600080fd5b8151600160201b81118282018710171561529d57600080fd5b5050929190505050846152af86618171565b6040518463ffffffff1660e01b815260040180806020018060200180602001848103845287818151815260200191508051906020019080838360005b838110156153035781810151838201526020016152eb565b50505050905090810190601f1680156153305780820380516001836020036101000a031916815260200191505b50848103835286518152865160209182019188019080838360005b8381101561536357818101518382015260200161534b565b50505050905090810190601f1680156153905780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b838110156153c35781810151838201526020016153ab565b50505050905090810190601f1680156153f05780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038186803b158015613faa57600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561545f57600080fd5b505afa158015615473573d6000803e3d6000fd5b505050506040513d602081101561548957600080fd5b50516001600160a01b031633146154d457604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b806154de81616372565b151560011461552157604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526155bb93909290918301828280156155ae5780601f10615583576101008083540402835291602001916155ae565b820191906000526020600020905b81548152906001019060200180831161559157829003601f168201915b5050505050836005617ec6565b1561577457600360009054906101000a90046001600160a01b03166001600160a01b03166337d50b27878787878c60056040518763ffffffff1660e01b81526004018080602001806020018761ffff1661ffff1681526020018661ffff1661ffff1681526020018060200185815260200184810384528a818151815260200191508051906020019080838360005b83811015615661578181015183820152602001615649565b50505050905090810190601f16801561568e5780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b838110156156c15781810151838201526020016156a9565b50505050905090810190601f1680156156ee5780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b83811015615721578181015183820152602001615709565b50505050905090810190601f16801561574e5780820380516001836020036101000a031916815260200191505b509950505050505050505050600060405180830381600087803b15801561202657600080fd5b50505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156157cb57600080fd5b505afa1580156157df573d6000803e3d6000fd5b505050506040513d60208110156157f557600080fd5b50516001600160a01b0316331461584057604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460009060ff161561588c5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b60048054604051600160e01b639e58eb9f028152602481018690526044810185905260609281019283526006805460026000196001831615610100020190911604606483018190526001600160a01b0390931693639e58eb9f93919288928892918291608490910190869080156159445780601f1061591957610100808354040283529160200191615944565b820191906000526020600020905b81548152906001019060200180831161592757829003601f168201915b5050945050505050600060405180830381600087803b15801561596657600080fd5b505af115801561597a573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526007805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b71357996509460069490928392918291602481019160c49091019089908015615a475780601f10615a1c57610100808354040283529160200191615a47565b820191906000526020600020905b815481529060010190602001808311615a2a57829003601f168201915b5050838103825287546002600019610100600184161502019091160480825260209091019088908015615abb5780601f10615a9057610100808354040283529160200191615abb565b820191906000526020600020905b815481529060010190602001808311615a9e57829003601f168201915b5050975050505050505050600060405180830381600087803b158015615ae057600080fd5b505af1158015615af4573d6000803e3d6000fd5b505060005460408051600160e01b63cef7f6af028152600481019182526007805460026000196001831615610100020190911604604483018190526001600160a01b03909416955063cef7f6af94509260089291829160248201916064019086908015615ba25780601f10615b7757610100808354040283529160200191615ba2565b820191906000526020600020905b815481529060010190602001808311615b8557829003601f168201915b5050838103825284546002600019610100600184161502019091160480825260209091019085908015615c165780601f10615beb57610100808354040283529160200191615c16565b820191906000526020600020905b815481529060010190602001808311615bf957829003601f168201915b5050945050505050600060405180830381600087803b158015615c3857600080fd5b505af1158015615774573d6000803e3d6000fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015615c9a57600080fd5b505afa158015615cae573d6000803e3d6000fd5b505050506040513d6020811015615cc457600080fd5b50516001600160a01b03163314615d0f57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b80615d1981616372565b1515600114615d5c57604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b8260011480615d6b5750826002145b1515615dc15760408051600160e51b62461bcd02815260206004820152601560248201527f4f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b6000808460011415615dd95750600290506003615dea565b8460021415615dea57506003905060055b615e2b87878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508592506182ad915050565b1515600114615e845760408051600160e51b62461bcd02815260206004820152601560248201527f6f7065726174696f6e206e6f7420616c6c6f7765640000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152615f1d9390929091830182828015615f115780601f10615ee657610100808354040283529160200191615f11565b820191906000526020600020905b815481529060010190602001808311615ef457829003601f168201915b50505050508584617ec6565b15615774576004805460408051600160e01b6314f775f902815260248101899052928301908152604483018990526001600160a01b03909116916314f775f9918a918a918a918190606401858580828437600081840152601f19601f820116905080830192505050945050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015615ff357600080fd5b505afa158015616007573d6000803e3d6000fd5b505050506040513d602081101561601d57600080fd5b50516001600160a01b0316331461606857604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b6160728188615059565b15156001146160b557604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b81600114806160c45750816002145b806160cf5750816003145b151561610f57604051600160e51b62461bcd0281526004018080602001828103825260258152602001806183e96025913960400191505060405180910390fd5b600354604051600160e01b6337d50b2702815261ffff80871660448301528516606482015260a4810184905260c060048201908152885160c483015288516001600160a01b03909316926337d50b27928a928a928a928a928f928b9282916024820191608481019160e49091019060208c0190808383600083811015615661578181015183820152602001615649565b600a5460068054604080516020601f6002600019600187161561010002019095169490940493840181900481028201810190925282815260609485948594600094919360079360089360ff9091169286918301828280156162415780601f1061621657610100808354040283529160200191616241565b820191906000526020600020905b81548152906001019060200180831161622457829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156162cf5780601f106162a4576101008083540402835291602001916162cf565b820191906000526020600020905b8154815290600101906020018083116162b257829003601f168201915b5050855460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529598508794509250840190508282801561635d5780601f106163325761010080835404028352916020019161635d565b820191906000526020600020905b81548152906001019060200180831161634057829003601f168201915b50505050509150935093509350935090919293565b604080516020808201908152600780546002600019610100600184161502019091160493830184905260009390928291606090910190849080156163f75780601f106163cc576101008083540402835291602001916163f7565b820191906000526020600020905b8154815290600101906020018083116163da57829003601f168201915b505060408051601f19818403018152828252805160209091012060008054600160e01b6381d66b230285526001600160a01b038a8116600487015293519297509290921694506381d66b239350602480840193829003018186803b15801561645e57600080fd5b505afa158015616472573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561649b57600080fd5b810190808051600160201b8111156164b257600080fd5b820160208101848111156164c557600080fd5b8151600160201b8111828201871017156164de57600080fd5b50509291905050506040516020018080602001828103825283818151815260200191508051906020019080838360005b8381101561652657818101518382015260200161650e565b50505050905090810190601f1680156165535780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120149050919050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156165c457600080fd5b505afa1580156165d8573d6000803e3d6000fd5b505050506040513d60208110156165ee57600080fd5b50516001600160a01b0316331461663957604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8061664381616372565b151560011461668657604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b600360009054906101000a90046001600160a01b03166001600160a01b03166337d50b27878787878c60046040518763ffffffff1660e01b81526004018080602001806020018761ffff1661ffff1681526020018661ffff1661ffff1681526020018060200185815260200184810384528a818151815260200191508051906020019080838360005b8381101561672757818101518382015260200161670f565b50505050905090810190601f1680156167545780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b8381101561678757818101518382015260200161676f565b50505050905090810190601f1680156167b45780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b838110156167e75781810151838201526020016167cf565b50505050905090810190601f1680156168145780820380516001836020036101000a031916815260200191505b509950505050505050505050600060405180830381600087803b15801561683a57600080fd5b505af115801561684e573d6000803e3d6000fd5b505060028054604051600160e01b63e98ac22d02815260006064820181905260056084830181905260a0600484019081526006805460001960018216156101000201169690960460a485018190526001600160a01b03909516975063e98ac22d96508e948e9482916024820191604481019160c4909101908a9080156169155780601f106168ea57610100808354040283529160200191616915565b820191906000526020600020905b8154815290600101906020018083116168f857829003601f168201915b505084810383528851815288516020918201918a019080838360005b83811015616949578181015183820152602001616931565b50505050905090810190601f1680156169765780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b838110156169a9578181015183820152602001616991565b50505050905090810190601f1680156169d65780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b158015616a4957600080fd5b505afa158015616a5d573d6000803e3d6000fd5b505050506040513d6020811015616a7357600080fd5b50516001600160a01b03163314616abe57604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b600a5460ff161515600114616b0b5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b616b1481616372565b1515600114616b5757604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b60028054604051600160e01b63e98ac22d0281526001600160a01b03858116606483015260016084830181905260a06004840190815260068054600019818516156101000201169690960460a48501819052929094169463e98ac22d9490938d938d938a939092909182916024810191604482019160c401908a908015616c1f5780601f10616bf457610100808354040283529160200191616c1f565b820191906000526020600020905b815481529060010190602001808311616c0257829003601f168201915b505084810383528851815288516020918201918a019080838360005b83811015616c53578181015183820152602001616c3b565b50505050905090810190601f168015616c805780820380516001836020036101000a031916815260200191505b50848103825287518152875160209182019189019080838360005b83811015616cb3578181015183820152602001616c9b565b50505050905090810190601f168015616ce05780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b158015616d0557600080fd5b505af1158015616d19573d6000803e3d6000fd5b505060048054604051600160e01b63f9953de502815260209281018381528c5160248301528c516001600160a01b03909316955063f9953de594508c93909283926044019185019080838360005b83811015616d7f578181015183820152602001616d67565b50505050905090810190601f168015616dac5780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b158015616dcb57600080fd5b505af1158015616ddf573d6000803e3d6000fd5b5050600354604051600160e01b63549583df02815261ffff80891660448301528716606482015260a0600482019081528a5160a48301528a516001600160a01b03909316945063549583df93508a928a928a928a928f9282916024820191608481019160c49091019060208b019080838360005b83811015616e6b578181015183820152602001616e53565b50505050905090810190601f168015616e985780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015616ecb578181015183820152602001616eb3565b50505050905090810190601f168015616ef85780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015616f2b578181015183820152602001616f13565b50505050905090810190601f168015616f585780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b158015616f7d57600080fd5b505af1158015616f91573d6000803e3d6000fd5b50505050616f9f8288613ef1565b1515600114616ff85760408051600160e51b62461bcd02815260206004820152601d60248201527f4f7065726174696f6e2063616e6e6f7420626520706572666f726d6564000000604482015290519081900360640190fd5b60008054604051600160e01b63e3483a9d0281526001600160a01b03858116600483019081526001606484018190526080602485019081528d5160848601528d51939095169563e3483a9d9589958f9560089593604483019260a4019160208901918190849084905b83811015617079578181015183820152602001617061565b50505050905090810190601f1680156170a65780820380516001836020036101000a031916815260200191505b508381038252855460026000196101006001841615020190911604808252602090910190869080156171195780601f106170ee57610100808354040283529160200191617119565b820191906000526020600020905b8154815290600101906020018083116170fc57829003601f168201915b50509650505050505050600060405180830381600087803b15801561202657600080fd5b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561718b57600080fd5b505afa15801561719f573d6000803e3d6000fd5b505050506040513d60208110156171b557600080fd5b50516001600160a01b0316331461720057604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b8561720a81617d12565b15156001146172515760408051600160e51b62461bcd02815260206004820152601a60248201526000805160206183a9833981519152604482015290519081900360640190fd5b61725b8288615059565b151560011461729e57604051600160e51b62461bcd02815260040180806020018281038252602281526020018061845c6022913960400191505060405180910390fd5b600354604051600160e01b634c57331102815261ffff80871660448301528516606482015260a060048201908152885160a483015288516001600160a01b0390931692634c573311928a928a928a928a928f92909182916024820191608481019160c49091019060208b019080838360005b83811015617328578181015183820152602001617310565b50505050905090810190601f1680156173555780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015617388578181015183820152602001617370565b50505050905090810190601f1680156173b55780820380516001836020036101000a031916815260200191505b5084810382528551815285516020918201918701908083836000838110156169a9578181015183820152602001616991565b600254604051600160e21b62539ab302815260206004820190815260248201849052606092839260009283926001600160a01b03169163014e6acc9189918991908190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561746757600080fd5b505afa15801561747b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156174a457600080fd5b810190808051600160201b8111156174bb57600080fd5b820160208101848111156174ce57600080fd5b8151600160201b8111828201871017156174e757600080fd5b50509291906020018051600160201b81111561750257600080fd5b8201602081018481111561751557600080fd5b8151600160201b81118282018710171561752e57600080fd5b50506020820151604090920151949b909a5090985092965091945050505050565b6005546001600160a01b031633146175b15760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600a5460009060ff16156175fd5760408051600160e51b62461bcd02815260206004820152601d60248201526000805160206183c9833981519152604482015290519081900360640190fd5b6176096006898961831a565b506176166007878761831a565b506176236008858561831a565b5050600a805460ff1916911515919091179055505050505050565b600560009054906101000a90046001600160a01b03166001600160a01b031663e572515c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561768c57600080fd5b505afa1580156176a0573d6000803e3d6000fd5b505050506040513d60208110156176b657600080fd5b50516001600160a01b0316331461770157604051600160e51b62461bcd02815260040180806020018281038252602881526020018061840e6028913960400191505060405180910390fd5b61770a81616372565b151560011461774d57604051600160e51b62461bcd0281526004018080602001828103825260268152602001806184366026913960400191505060405180910390fd5b6177588760016182ad565b15156001146177b15760408051600160e51b62461bcd02815260206004820152601260248201527f4e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b60068054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815261784b939092909183018282801561783e5780601f106178135761010080835404028352916020019161783e565b820191906000526020600020905b81548152906001019060200180831161782157829003601f168201915b5050505050826001617ec6565b156157745760048054604051600160e11b637181418b02815260209281018381528a5160248301528a516001600160a01b039093169363e3028316938c9383926044909101919085019080838360005b838110156178b357818101518382015260200161789b565b50505050905090810190601f1680156178e05780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b1580156178ff57600080fd5b505af1158015617913573d6000803e3d6000fd5b505060018054600954604051600160e01b637b71357902815260448101829052606481018490526084810184905260a0600482019081526008805460026000198289161561010002019091160460a484018190526001600160a01b039095169750637b7135799650948e9490928392918291602481019160c490910190899080156179df5780601f106179b4576101008083540402835291602001916179df565b820191906000526020600020905b8154815290600101906020018083116179c257829003601f168201915b5050838103825287518152875160209182019189019080838360005b83811015617a135781810151838201526020016179fb565b50505050905090810190601f168015617a405780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b158015617a6457600080fd5b505af1158015617a78573d6000803e3d6000fd5b5050600354604051600160e21b633e0b822b02815261ffff80891660448301528716606482015260a0600482019081528a5160a48301528a516001600160a01b03909316945063f82e08ac93508a928a928a928a928f9282916024820191608481019160c49091019060208b019080838360005b83811015617b04578181015183820152602001617aec565b50505050905090810190601f168015617b315780820380516001836020036101000a031916815260200191505b5084810383528851815288516020918201918a019080838360005b83811015617b64578181015183820152602001617b4c565b50505050905090810190601f168015617b915780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015617bc4578181015183820152602001617bac565b50505050905090810190601f168015617bf15780820380516001836020036101000a031916815260200191505b5098505050505050505050600060405180830381600087803b158015617c1657600080fd5b505af1158015617c2a573d6000803e3d6000fd5b50506000805460408051600160e01b63c214e5e50281526001600160a01b038881166024830152600482019283528d5160448301528d519316955063c214e5e594508c9388938392606401916020870191908190849084905b83811015617c9b578181015183820152602001617c83565b50505050905090810190601f168015617cc85780820380516001836020036101000a031916815260200191505b509350505050602060405180830381600087803b158015617ce857600080fd5b505af1158015617cfc573d6000803e3d6000fd5b505050506040513d602081101561273c57600080fd5b6004805460408051600160e01b638c8642df0281526002602482018190529381019182528451604482015284516000946001600160a01b0390941693638c8642df93879391929091829160649091019060208601908083838c5b83811015617d84578181015183820152602001617d6c565b50505050905090810190601f168015617db15780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b158015617dcf57600080fd5b505afa158015617de3573d6000803e3d6000fd5b505050506040513d6020811015617df957600080fd5b505192915050565b600480546040517fffe40d1d00000000000000000000000000000000000000000000000000000000815260209281018381528451602483015284516000946001600160a01b039094169363ffe40d1d9387939283926044909201918501908083838b5b83811015617e7c578181015183820152602001617e64565b50505050905090810190601f168015617ea95780820380516001836020036101000a031916815260200191505b509250505060206040518083038186803b158015617dcf57600080fd5b600254604051600160e21b632c084e190281526001600160a01b03848116602483015260448201849052606060048301908152865160648401528651600094929092169263b02138649288928892889282916084019060208701908083838d5b83811015617f3e578181015183820152602001617f26565b50505050905090810190601f168015617f6b5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015617f8c57600080fd5b505af1158015617fa0573d6000803e3d6000fd5b505050506040513d6020811015617fb657600080fd5b5051949350505050565b801561809a5760025460408051600160e01b635607395b0281526001600160a01b03858116602483015260048201928352865160448301528651931692635607395b9287928792829160640190602086019080838360005b83811015618030578181015183820152602001618018565b50505050905090810190601f16801561805d5780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b15801561807d57600080fd5b505af1158015618091573d6000803e3d6000fd5b50505050618151565b60025460408051600160e11b632ce5eb7f0281526001600160a01b038581166024830152600482019283528651604483015286519316926359cbd6fe9287928792829160640190602086019080838360005b838110156181045781810151838201526020016180ec565b50505050905090810190601f1680156181315780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b158015615c3857600080fd5b505050565b6001546000906001600160a01b031663abf5739f84846152af815b60048054604051600160e11b630bbe46c502815260209281018381528451602483015284516060946001600160a01b039094169363177c8d8a93879392839260449092019185019080838360005b838110156181d75781810151838201526020016181bf565b50505050905090810190601f1680156182045780820380516001836020036101000a031916815260200191505b509250505060006040518083038186803b15801561822157600080fd5b505afa158015618235573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561825e57600080fd5b810190808051600160201b81111561827557600080fd5b8201602081018481111561828857600080fd5b8151600160201b8111828201871017156182a157600080fd5b50909695505050505050565b6004805460408051600160e01b638c8642df028152602481018590529283019081528451604484015284516000936001600160a01b0390931692638c8642df9287928792829160649091019060208601908083838c83811015613f5f578181015183820152602001613f47565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061835b5782800160ff19823516178555618388565b82800160010185558215618388579182015b8281111561838857823582559160200191906001019061836d565b50612f68926132c09250905b80821115612f68576000815560010161839456fe6f7267206e6f7420696e20617070726f76656420737461747573000000000000496e636f7272656374206e6574776f726b20626f6f7420737461747573000000696e76616c696420616374696f6e2e206f7065726174696f6e206e6f7420616c6c6f77656463616e2062652063616c6c656420627920696e7465726661636520636f6e7472616374206f6e6c796163636f756e74206973206e6f742061206e6574776f726b2061646d696e206163636f756e746163636f756e74206973206e6f742061206f72672061646d696e206163636f756e74a165627a7a72305820076e128f2df9e9b08b731798802e430db87855f1ea7dd2b592784c23ef6897aa0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/PermissionsInterface.abi b/permission/v2/contract/gen/PermissionsInterface.abi new file mode 100644 index 0000000000..e0c7558d9d --- /dev/null +++ b/permission/v2/contract/gen/PermissionsInterface.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"getPermissionsImpl","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"approveAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_nwAdminOrg","type":"string"},{"name":"_nwAdminRole","type":"string"},{"name":"_oAdminRole","type":"string"}],"name":"setPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_pOrgId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"}],"name":"addSubOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"},{"name":"_roleId","type":"string"}],"name":"assignAccountRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"approveBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_action","type":"uint256"}],"name":"updateNodeStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_roleId","type":"string"}],"name":"assignAdminRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"}],"name":"connectionAllowed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNetworkBootStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_acct","type":"address"}],"name":"addAdminAccount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_permImplementation","type":"address"}],"name":"setPermImplementation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_account","type":"address"}],"name":"addOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_access","type":"uint256"},{"name":"_voter","type":"bool"},{"name":"_admin","type":"bool"}],"name":"addNewRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"}],"name":"approveBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"approveOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"validateAccount","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"},{"name":"_action","type":"uint256"}],"name":"updateAccountStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"}],"name":"addAdminNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"}],"name":"startBlacklistedNodeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_target","type":"address"},{"name":"_value","type":"uint256"},{"name":"_gasPrice","type":"uint256"},{"name":"_gasLimit","type":"uint256"},{"name":"_payload","type":"bytes"}],"name":"transactionAllowed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"},{"name":"_orgId","type":"string"}],"name":"isOrgAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_breadth","type":"uint256"},{"name":"_depth","type":"uint256"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"}],"name":"removeRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_account","type":"address"}],"name":"startBlacklistedAccountRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_action","type":"uint256"}],"name":"updateOrgStatus","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"isNetworkAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"}],"name":"addNode","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getPendingOp","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_ip","type":"string"},{"name":"_port","type":"uint16"},{"name":"_raftport","type":"uint16"},{"name":"_account","type":"address"}],"name":"approveOrg","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permImplUpgradeable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/PermissionsInterface.bin b/permission/v2/contract/gen/PermissionsInterface.bin new file mode 100644 index 0000000000..894fd57bef --- /dev/null +++ b/permission/v2/contract/gen/PermissionsInterface.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516020806134518339810180604052602081101561003057600080fd5b5051600280546001600160a01b0319166001600160a01b039092169190911790556133f1806100606000396000f3fe608060405234801561001057600080fd5b50600436106101e55760003560e01c806358dcff711161010f578063a5843f08116100a2578063d1aa0c2011610071578063d1aa0c2014611673578063ef5f719614611699578063f346a3a714611858578063fa279d61146119c4576101e5565b8063a5843f08146114ad578063a6343012146114d0578063a97914bf1461158e578063bb3b6e8014611605576101e5565b80638683c7fe116100de5780638683c7fe1461109c57806391ba3f96146111d6578063936421d5146113955780639bd381011461142f576101e5565b806358dcff7114610d745780635be9672c14610f335780636b568d7614610fa157806384b7a84a1461101f576101e5565b806343de646c116101875780634fe57e7a116101565780634fe57e7a14610a90578063511bbd9f14610ab6578063513a327714610adc57806351f604c314610ca6576101e5565b806343de646c146108df57806344478e79146109aa57806345a59e5b146109c65780634cbfa82e14610a88576101e5565b80632e125a6c116101c35780632e125a6c146103955780632f7f0a12146105d95780633e239b23146106a75780633f9be4971461071e576101e5565b806303ed6933146101ea57806316724c441461020e5780631b61022014610287575b600080fd5b6101f2611b8e565b604080516001600160a01b039092168252519081900360200190f35b6102856004803603604081101561022457600080fd5b810190602081018135600160201b81111561023e57600080fd5b82018360208201111561025057600080fd5b803590602001918460018302840111600160201b8311171561027157600080fd5b9193509150356001600160a01b0316611b9d565b005b6102856004803603606081101561029d57600080fd5b810190602081018135600160201b8111156102b757600080fd5b8201836020820111156102c957600080fd5b803590602001918460018302840111600160201b831117156102ea57600080fd5b919390929091602081019035600160201b81111561030757600080fd5b82018360208201111561031957600080fd5b803590602001918460018302840111600160201b8311171561033a57600080fd5b919390929091602081019035600160201b81111561035757600080fd5b82018360208201111561036957600080fd5b803590602001918460018302840111600160201b8311171561038a57600080fd5b509092509050611c4c565b610285600480360360c08110156103ab57600080fd5b810190602081018135600160201b8111156103c557600080fd5b8201836020820111156103d757600080fd5b803590602001918460018302840111600160201b831117156103f857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561044a57600080fd5b82018360208201111561045c57600080fd5b803590602001918460018302840111600160201b8311171561047d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156104cf57600080fd5b8201836020820111156104e157600080fd5b803590602001918460018302840111600160201b8311171561050257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561055457600080fd5b82018360208201111561056657600080fd5b803590602001918460018302840111600160201b8311171561058757600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff8335811694506020909301359092169150611d439050565b610285600480360360608110156105ef57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561061957600080fd5b82018360208201111561062b57600080fd5b803590602001918460018302840111600160201b8311171561064c57600080fd5b919390929091602081019035600160201b81111561066957600080fd5b82018360208201111561067b57600080fd5b803590602001918460018302840111600160201b8311171561069c57600080fd5b509092509050611f52565b610285600480360360408110156106bd57600080fd5b810190602081018135600160201b8111156106d757600080fd5b8201836020820111156106e957600080fd5b803590602001918460018302840111600160201b8311171561070a57600080fd5b9193509150356001600160a01b0316612033565b610285600480360360c081101561073457600080fd5b810190602081018135600160201b81111561074e57600080fd5b82018360208201111561076057600080fd5b803590602001918460018302840111600160201b8311171561078157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156107d357600080fd5b8201836020820111156107e557600080fd5b803590602001918460018302840111600160201b8311171561080657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561085857600080fd5b82018360208201111561086a57600080fd5b803590602001918460018302840111600160201b8311171561088b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff833581169450602084013516926040013591506120c59050565b610285600480360360608110156108f557600080fd5b810190602081018135600160201b81111561090f57600080fd5b82018360208201111561092157600080fd5b803590602001918460018302840111600160201b8311171561094257600080fd5b919390926001600160a01b0383351692604081019060200135600160201b81111561096c57600080fd5b82018360208201111561097e57600080fd5b803590602001918460018302840111600160201b8311171561099f57600080fd5b509092509050612274565b6109b2612333565b604080519115158252519081900360200190f35b6109b2600480360360608110156109dc57600080fd5b810190602081018135600160201b8111156109f657600080fd5b820183602082011115610a0857600080fd5b803590602001918460018302840111600160201b83111715610a2957600080fd5b919390929091602081019035600160201b811115610a4657600080fd5b820183602082011115610a5857600080fd5b803590602001918460018302840111600160201b83111715610a7957600080fd5b91935091503561ffff166123b5565b6109b26124a1565b61028560048036036020811015610aa657600080fd5b50356001600160a01b0316612504565b61028560048036036020811015610acc57600080fd5b50356001600160a01b031661256d565b610285600480360360c0811015610af257600080fd5b810190602081018135600160201b811115610b0c57600080fd5b820183602082011115610b1e57600080fd5b803590602001918460018302840111600160201b83111715610b3f57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610b9157600080fd5b820183602082011115610ba357600080fd5b803590602001918460018302840111600160201b83111715610bc457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610c1657600080fd5b820183602082011115610c2857600080fd5b803590602001918460018302840111600160201b83111715610c4957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b031690506125f1565b610285600480360360a0811015610cbc57600080fd5b810190602081018135600160201b811115610cd657600080fd5b820183602082011115610ce857600080fd5b803590602001918460018302840111600160201b83111715610d0957600080fd5b919390929091602081019035600160201b811115610d2657600080fd5b820183602082011115610d3857600080fd5b803590602001918460018302840111600160201b83111715610d5957600080fd5b91935091508035906020810135151590604001351515612691565b610285600480360360a0811015610d8a57600080fd5b810190602081018135600160201b811115610da457600080fd5b820183602082011115610db657600080fd5b803590602001918460018302840111600160201b83111715610dd757600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610e2957600080fd5b820183602082011115610e3b57600080fd5b803590602001918460018302840111600160201b83111715610e5c57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610eae57600080fd5b820183602082011115610ec057600080fd5b803590602001918460018302840111600160201b83111715610ee157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff83358116945060209093013590921691506127869050565b61028560048036036040811015610f4957600080fd5b810190602081018135600160201b811115610f6357600080fd5b820183602082011115610f7557600080fd5b803590602001918460018302840111600160201b83111715610f9657600080fd5b91935091503561292f565b6109b260048036036040811015610fb757600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610fe157600080fd5b820183602082011115610ff357600080fd5b803590602001918460018302840111600160201b8311171561101457600080fd5b5090925090506129bc565b6102856004803603606081101561103557600080fd5b810190602081018135600160201b81111561104f57600080fd5b82018360208201111561106157600080fd5b803590602001918460018302840111600160201b8311171561108257600080fd5b91935091506001600160a01b038135169060200135612a74565b610285600480360360808110156110b257600080fd5b810190602081018135600160201b8111156110cc57600080fd5b8201836020820111156110de57600080fd5b803590602001918460018302840111600160201b831117156110ff57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561115157600080fd5b82018360208201111561116357600080fd5b803590602001918460018302840111600160201b8311171561118457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff8335811694506020909301359092169150612b2c9050565b610285600480360360a08110156111ec57600080fd5b810190602081018135600160201b81111561120657600080fd5b82018360208201111561121857600080fd5b803590602001918460018302840111600160201b8311171561123957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561128b57600080fd5b82018360208201111561129d57600080fd5b803590602001918460018302840111600160201b831117156112be57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561131057600080fd5b82018360208201111561132257600080fd5b803590602001918460018302840111600160201b8311171561134357600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff8335811694506020909301359092169150612c629050565b6109b2600480360360c08110156113ab57600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b8111156113f157600080fd5b82018360208201111561140357600080fd5b803590602001918460018302840111600160201b8311171561142457600080fd5b509092509050612cf7565b6109b26004803603604081101561144557600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561146f57600080fd5b82018360208201111561148157600080fd5b803590602001918460018302840111600160201b831117156114a257600080fd5b509092509050612dde565b610285600480360360408110156114c357600080fd5b5080359060200135612e62565b610285600480360360408110156114e657600080fd5b810190602081018135600160201b81111561150057600080fd5b82018360208201111561151257600080fd5b803590602001918460018302840111600160201b8311171561153357600080fd5b919390929091602081019035600160201b81111561155057600080fd5b82018360208201111561156257600080fd5b803590602001918460018302840111600160201b8311171561158357600080fd5b509092509050612ed0565b610285600480360360408110156115a457600080fd5b810190602081018135600160201b8111156115be57600080fd5b8201836020820111156115d057600080fd5b803590602001918460018302840111600160201b831117156115f157600080fd5b9193509150356001600160a01b0316612f84565b6102856004803603604081101561161b57600080fd5b810190602081018135600160201b81111561163557600080fd5b82018360208201111561164757600080fd5b803590602001918460018302840111600160201b8311171561166857600080fd5b919350915035613016565b6109b26004803603602081101561168957600080fd5b50356001600160a01b03166130a3565b610285600480360360a08110156116af57600080fd5b810190602081018135600160201b8111156116c957600080fd5b8201836020820111156116db57600080fd5b803590602001918460018302840111600160201b831117156116fc57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561174e57600080fd5b82018360208201111561176057600080fd5b803590602001918460018302840111600160201b8311171561178157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156117d357600080fd5b8201836020820111156117e557600080fd5b803590602001918460018302840111600160201b8311171561180657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505061ffff83358116945060209093013590921691506131269050565b6118c66004803603602081101561186e57600080fd5b810190602081018135600160201b81111561188857600080fd5b82018360208201111561189a57600080fd5b803590602001918460018302840111600160201b831117156118bb57600080fd5b5090925090506131bb565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b8381101561192557818101518382015260200161190d565b50505050905090810190601f1680156119525780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b8381101561198557818101518382015260200161196d565b50505050905090810190601f1680156119b25780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b610285600480360360c08110156119da57600080fd5b810190602081018135600160201b8111156119f457600080fd5b820183602082011115611a0657600080fd5b803590602001918460018302840111600160201b83111715611a2757600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611a7957600080fd5b820183602082011115611a8b57600080fd5b803590602001918460018302840111600160201b83111715611aac57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115611afe57600080fd5b820183602082011115611b1057600080fd5b803590602001918460018302840111600160201b83111715611b3157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813561ffff908116935060208301351691604001356001600160a01b03169050613325565b6000546001600160a01b031690565b600054604051600160e01b63888430410281526001600160a01b03838116602483015233604483018190526060600484019081526064840187905291909316926388843041928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b505af1158015611c43573d6000803e3d6000fd5b50505050505050565b600054604051600160e51b62db0811028152606060048201908152606482018890526001600160a01b0390921691631b610220918991899189918991899189918190602481019060448101906084018a8a80828437600083820152601f01601f191690910185810384528881526020019050888880828437600083820152601f01601f191690910185810383528681526020019050868680828437600081840152601f19601f8201169050808301925050509950505050505050505050600060405180830381600087803b158015611d2357600080fd5b505af1158015611d37573d6000803e3d6000fd5b50505050505050505050565b60008054604051600160e01b6368a6127302815261ffff8086166084830152841660a48201523360c4820181905260e0600483019081528a5160e48401528a516001600160a01b03909416946368a61273948c948c948c948c948c948c9483926024820192604483019260648101926101049091019160208f01918190849084905b83811015611ddd578181015183820152602001611dc5565b50505050905090810190601f168015611e0a5780820380516001836020036101000a031916815260200191505b5085810384528b5181528b516020918201918d019080838360005b83811015611e3d578181015183820152602001611e25565b50505050905090810190601f168015611e6a5780820380516001836020036101000a031916815260200191505b5085810383528a5181528a516020918201918c019080838360005b83811015611e9d578181015183820152602001611e85565b50505050905090810190601f168015611eca5780820380516001836020036101000a031916815260200191505b5085810382528951815289516020918201918b019080838360005b83811015611efd578181015183820152602001611ee5565b50505050905090810190601f168015611f2a5780820380516001836020036101000a031916815260200191505b509b505050505050505050505050600060405180830381600087803b158015611d2357600080fd5b600054604051600160e01b638baa81910281526001600160a01b03878116600483019081523360648401819052608060248501908152608485018990529290941693638baa8191938a938a938a938a938a9391929190604481019060a401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b15801561201457600080fd5b505af1158015612028573d6000803e3d6000fd5b505050505050505050565b600054604051600160e01b634b20f45f0281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692634b20f45f928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b60008054604051600160e21b632e6dff9b02815261ffff80871660648301528516608482015260a481018490523360c4820181905260e0600483019081528a5160e48401528a516001600160a01b039094169463b9b7fe6c948c948c948c948c948c948c94839260248201926044830192610104019160208e0191908190849084905b83811015612160578181015183820152602001612148565b50505050905090810190601f16801561218d5780820380516001836020036101000a031916815260200191505b5084810383528a5181528a516020918201918c019080838360005b838110156121c05781810151838201526020016121a8565b50505050905090810190601f1680156121ed5780820380516001836020036101000a031916815260200191505b5084810382528951815289516020918201918b019080838360005b83811015612220578181015183820152602001612208565b50505050905090810190601f16801561224d5780820380516001836020036101000a031916815260200191505b509a5050505050505050505050600060405180830381600087803b158015611d2357600080fd5b600054604051600160e01b63404bf3eb0281526001600160a01b038581166024830152336064830181905260806004840190815260848401899052919093169263404bf3eb9289928992899289928992918190604481019060a401898980828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505098505050505050505050600060405180830381600087803b15801561201457600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b03166344478e796040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561238457600080fd5b505af1158015612398573d6000803e3d6000fd5b505050506040513d60208110156123ae57600080fd5b5051905090565b60008054604051600160e01b6345a59e5b02815261ffff84166044820152606060048201908152606482018890526001600160a01b03909216916345a59e5b91899189918991899189919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f82011690508083019250505097505050505050505060206040518083038186803b15801561246b57600080fd5b505afa15801561247f573d6000803e3d6000fd5b505050506040513d602081101561249557600080fd5b50519695505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316634cbfa82e6040518163ffffffff1660e01b815260040160206040518083038186803b1580156124f057600080fd5b505afa158015612398573d6000803e3d6000fd5b6000805460408051600160e11b6327f2bf3d0281526001600160a01b03858116600483015291519190921692634fe57e7a926024808201939182900301818387803b15801561255257600080fd5b505af1158015612566573d6000803e3d6000fd5b5050505050565b6002546001600160a01b031633146125cf5760408051600160e51b62461bcd02815260206004820152600e60248201527f696e76616c69642063616c6c6572000000000000000000000000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b60008054604051600160e01b63e91b0e1902815261ffff8087166064830152851660848201526001600160a01b0384811660a48301523360c4830181905260e0600484019081528b5160e48501528b51929094169463e91b0e19948c948c948c948c948c948c9492939092839260248201926044830192610104019160208e01919081908490849083811015612160578181015183820152602001612148565b600054604051600160e11b630d82613b02815260448101859052831515606482015282151560848201523360a4820181905260c06004830190815260c483018a90526001600160a01b0390931692631b04c276928b928b928b928b928b928b928b9291908190602481019060e4018b8b80828437600083820152601f01601f191690910184810383528981526020019050898980828437600081840152601f19601f8201169050808301925050509a5050505050505050505050600060405180830381600087803b15801561276557600080fd5b505af1158015612779573d6000803e3d6000fd5b5050505050505050505050565b60008054604051600160e61b6302810afd02815261ffff8086166064830152841660848201523360a4820181905260c060048301908152895160c484015289516001600160a01b039094169463a042bf40948b948b948b948b948b949293919283926024810192604482019260e49092019160208d01918190849084905b8381101561281c578181015183820152602001612804565b50505050905090810190601f1680156128495780820380516001836020036101000a031916815260200191505b5084810383528951815289516020918201918b019080838360005b8381101561287c578181015183820152602001612864565b50505050905090810190601f1680156128a95780820380516001836020036101000a031916815260200191505b5084810382528851815288516020918201918a019080838360005b838110156128dc5781810151838201526020016128c4565b50505050905090810190601f1680156129095780820380516001836020036101000a031916815260200191505b509950505050505050505050600060405180830381600087803b15801561201457600080fd5b600054604051600160e21b632d551959028152602481018390523360448201819052606060048301908152606483018690526001600160a01b039093169263b5546564928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b6000805460408051600160e11b6335ab46bb0281526001600160a01b03878116600483019081526024830193845260448301879052931692636b568d76928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015612a4057600080fd5b505afa158015612a54573d6000803e3d6000fd5b505050506040513d6020811015612a6a57600080fd5b5051949350505050565b600054604051600160e11b6302740f8f0281526001600160a01b0384811660248301526044820184905233606483018190526080600484019081526084840188905291909316926304e81f1e92889288928892889290819060a401878780828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b158015612b0e57600080fd5b505af1158015612b22573d6000803e3d6000fd5b5050505050505050565b60008054604051600160e11b634341e3ff02815261ffff8086166044830152841660648201526080600482019081528751608483015287516001600160a01b0390931693638683c7fe93899389938993899391928392602482019260a49092019160208a0191908190849084905b83811015612bb2578181015183820152602001612b9a565b50505050905090810190601f168015612bdf5780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015612c12578181015183820152602001612bfa565b50505050905090810190601f168015612c3f5780820380516001836020036101000a031916815260200191505b509650505050505050600060405180830381600087803b158015612b0e57600080fd5b60008054604051600160e01b63d621d95702815261ffff8086166064830152841660848201523360a4820181905260c060048301908152895160c484015289516001600160a01b039094169463d621d957948b948b948b948b948b949293919283926024810192604482019260e49092019160208d01918190849084908381101561281c578181015183820152602001612804565b60008054604051600160e01b63936421d50281526001600160a01b038a8116600483019081528a82166024840152604483018a9052606483018990526084830188905260c060a4840190815260c48401879052919093169263936421d5928c928c928c928c928c928c928c929060e401848480828437600081840152601f19601f8201169050808301925050509850505050505050505060206040518083038186803b158015612da657600080fd5b505afa158015612dba573d6000803e3d6000fd5b505050506040513d6020811015612dd057600080fd5b505198975050505050505050565b6000805460408051600160e01b639bd381010281526001600160a01b03878116600483019081526024830193845260448301879052931692639bd38101928892889288929091606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015612a4057600080fd5b6000805460408051600160e31b6314b087e1028152600481018690526024810185905290516001600160a01b039092169263a5843f089260448084019382900301818387803b158015612eb457600080fd5b505af1158015612ec8573d6000803e3d6000fd5b505050505050565b600054604051600160e11b632e52d6df0281523360448201819052606060048301908152606483018790526001600160a01b0390931692635ca5adbe928892889288928892919081906024810190608401888880828437600083820152601f01601f191690910184810383528681526020019050868680828437600081840152601f19601f820116905080830192505050975050505050505050600060405180830381600087803b158015612b0e57600080fd5b600054604051600160e11b630e124c890281526001600160a01b0383811660248301523360448301819052606060048401908152606484018790529190931692631c249912928792879287929091908190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b600054604051600160e01b633cf5f33b028152602481018390523360448201819052606060048301908152606483018690526001600160a01b0390931692633cf5f33b928792879287928190608401868680828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611c2f57600080fd5b6000805460408051600160e51b63068d50610281526001600160a01b0385811660048301529151919092169163d1aa0c20916024808301926020929190829003018186803b1580156130f457600080fd5b505afa158015613108573d6000803e3d6000fd5b505050506040513d602081101561311e57600080fd5b505192915050565b60008054604051600160e01b63ecad01d502815261ffff8086166064830152841660848201523360a4820181905260c060048301908152895160c484015289516001600160a01b039094169463ecad01d5948b948b948b948b948b949293919283926024810192604482019260e49092019160208d01918190849084908381101561281c578181015183820152602001612804565b60008054604051600160e01b63f346a3a7028152602060048201908152602482018590526060938493909283926001600160a01b039092169163f346a3a791899189918190604401848480828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561323d57600080fd5b505afa158015613251573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052608081101561327a57600080fd5b810190808051600160201b81111561329157600080fd5b820160208101848111156132a457600080fd5b8151600160201b8111828201871017156132bd57600080fd5b50509291906020018051600160201b8111156132d857600080fd5b820160208101848111156132eb57600080fd5b8151600160201b81118282018710171561330457600080fd5b50506020820151604090920151949b909a5090985092965091945050505050565b60008054604051600160e11b637baf850302815261ffff8087166064830152851660848201526001600160a01b0384811660a48301523360c4830181905260e0600484019081528b5160e48501528b51929094169463f75f0a06948c948c948c948c948c948c9492939092839260248201926044830192610104019160208e0191908190849084908381101561216057818101518382015260200161214856fea165627a7a72305820b6e6a59155f89147a70225e82521ad7432c887b50d3e810e125f0b82322d043f0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/PermissionsUpgradable.abi b/permission/v2/contract/gen/PermissionsUpgradable.abi new file mode 100644 index 0000000000..035f83a651 --- /dev/null +++ b/permission/v2/contract/gen/PermissionsUpgradable.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"getPermImpl","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_proposedImpl","type":"address"}],"name":"confirmImplChange","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getGuardian","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getPermInterface","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_permInterface","type":"address"},{"name":"_permImpl","type":"address"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_guardian","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/PermissionsUpgradable.bin b/permission/v2/contract/gen/PermissionsUpgradable.bin new file mode 100644 index 0000000000..7093f5907c --- /dev/null +++ b/permission/v2/contract/gen/PermissionsUpgradable.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516020806106e78339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905560028054600160a01b60ff0219169055610675806100726000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e32cf901461005c57806322bcb39a14610080578063a75b87d2146100a8578063e572515c146100b0578063f09a4016146100b8575b600080fd5b6100646100e6565b604080516001600160a01b039092168252519081900360200190f35b6100a66004803603602081101561009657600080fd5b50356001600160a01b03166100f5565b005b61006461030b565b61006461031a565b6100a6600480360360408110156100ce57600080fd5b506001600160a01b0381358116916020013516610329565b6001546001600160a01b031690565b6000546001600160a01b0316331461014b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60608060606000600160009054906101000a90046001600160a01b03166001600160a01b031663cc9ba6fa6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a057600080fd5b505afa1580156101b4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156101dd57600080fd5b8101908080516401000000008111156101f557600080fd5b8201602081018481111561020857600080fd5b815164010000000081118282018710171561022257600080fd5b5050929190602001805164010000000081111561023e57600080fd5b8201602081018481111561025157600080fd5b815164010000000081118282018710171561026b57600080fd5b5050929190602001805164010000000081111561028757600080fd5b8201602081018481111561029a57600080fd5b81516401000000008111828201871017156102b457600080fd5b50506020909101519498509296509194509192506102d9915086905085858585610443565b600180546001600160a01b0319166001600160a01b03878116919091179182905561030491166105e4565b5050505050565b6000546001600160a01b031690565b6002546001600160a01b031690565b6000546001600160a01b0316331461037f5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600254600160a01b900460ff16156103e15760408051600160e51b62461bcd02815260206004820152601960248201527f63616e206265206578656375746564206f6e6c79206f6e636500000000000000604482015290519081900360640190fd5b600180546001600160a01b038084166001600160a01b031992831617928390556002805486831693169290921790915561041b91166105e4565b50506002805474ff00000000000000000000000000000000000000001916600160a01b179055565b846001600160a01b031663f5ad584a858585856040518563ffffffff1660e01b81526004018080602001806020018060200185151515158152602001848103845288818151815260200191508051906020019080838360005b838110156104b457818101518382015260200161049c565b50505050905090810190601f1680156104e15780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156105145781810151838201526020016104fc565b50505050905090810190601f1680156105415780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b8381101561057457818101518382015260200161055c565b50505050905090810190601f1680156105a15780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156105c557600080fd5b505af11580156105d9573d6000803e3d6000fd5b505050505050505050565b60025460408051600160e01b63511bbd9f0281526001600160a01b0384811660048301529151919092169163511bbd9f91602480830192600092919082900301818387803b15801561063557600080fd5b505af1158015610304573d6000803e3d6000fdfea165627a7a7230582055489d1e43ffd1f6646b629ccf78d3fb7551dd246e111ec3ccbf9ae12f8b900a0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/RoleManager.abi b/permission/v2/contract/gen/RoleManager.abi new file mode 100644 index 0000000000..21d547c460 --- /dev/null +++ b/permission/v2/contract/gen/RoleManager.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"}],"name":"getRoleDetails","outputs":[{"name":"roleId","type":"string"},{"name":"orgId","type":"string"},{"name":"accessType","type":"uint256"},{"name":"voter","type":"bool"},{"name":"admin","type":"bool"},{"name":"active","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_baseAccess","type":"uint256"},{"name":"_isVoter","type":"bool"},{"name":"_isAdmin","type":"bool"}],"name":"addRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNumberOfRoles","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_rIndex","type":"uint256"}],"name":"getRoleDetailsFromIndex","outputs":[{"name":"roleId","type":"string"},{"name":"orgId","type":"string"},{"name":"accessType","type":"uint256"},{"name":"voter","type":"bool"},{"name":"admin","type":"bool"},{"name":"active","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"}],"name":"removeRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"roleExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"isAdminRole","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"roleAccess","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"},{"name":"_typeOfTxn","type":"uint256"}],"name":"transactionAllowed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_roleId","type":"string"},{"name":"_orgId","type":"string"},{"name":"_ultParent","type":"string"}],"name":"isVoterRole","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_baseAccess","type":"uint256"},{"indexed":false,"name":"_isVoter","type":"bool"},{"indexed":false,"name":"_isAdmin","type":"bool"}],"name":"RoleCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_roleId","type":"string"},{"indexed":false,"name":"_orgId","type":"string"}],"name":"RoleRevoked","type":"event"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/RoleManager.bin b/permission/v2/contract/gen/RoleManager.bin new file mode 100644 index 0000000000..8e6c6153ef --- /dev/null +++ b/permission/v2/contract/gen/RoleManager.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516020806128d98339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055612877806100626000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c8063abf5739f11610066578063abf5739f1461048e578063be322e5414610650578063cfc83dfa1461075e578063d1f778661461090c578063deb16ba714610a1a5761009e565b80631870aba3146100a35780637b7135791461025f57806387f55d3114610399578063a451d4a8146103b3578063a6343012146103d0575b600080fd5b610161600480360360408110156100b957600080fd5b810190602081018135600160201b8111156100d357600080fd5b8201836020820111156100e557600080fd5b803590602001918460018302840111600160201b8311171561010657600080fd5b919390929091602081019035600160201b81111561012357600080fd5b82018360208201111561013557600080fd5b803590602001918460018302840111600160201b8311171561015657600080fd5b509092509050610b28565b604080519081018590528315156060820152821515608082015281151560a082015260c08082528751908201528651819060208083019160e08401918b019080838360005b838110156101be5781810151838201526020016101a6565b50505050905090810190601f1680156101eb5780820380516001836020036101000a031916815260200191505b5083810382528851815288516020918201918a019080838360005b8381101561021e578181015183820152602001610206565b50505050905090810190601f16801561024b5780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390f35b610397600480360360a081101561027557600080fd5b810190602081018135600160201b81111561028f57600080fd5b8201836020820111156102a157600080fd5b803590602001918460018302840111600160201b831117156102c257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561031457600080fd5b82018360208201111561032657600080fd5b803590602001918460018302840111600160201b8311171561034757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550508235935050506020810135151590604001351515610eae565b005b6103a1611454565b60408051918252519081900360200190f35b610161600480360360208110156103c957600080fd5b503561145b565b610397600480360360408110156103e657600080fd5b810190602081018135600160201b81111561040057600080fd5b82018360208201111561041257600080fd5b803590602001918460018302840111600160201b8311171561043357600080fd5b919390929091602081019035600160201b81111561045057600080fd5b82018360208201111561046257600080fd5b803590602001918460018302840111600160201b8311171561048357600080fd5b509092509050611679565b61063c600480360360608110156104a457600080fd5b810190602081018135600160201b8111156104be57600080fd5b8201836020820111156104d057600080fd5b803590602001918460018302840111600160201b831117156104f157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561054357600080fd5b82018360208201111561055557600080fd5b803590602001918460018302840111600160201b8311171561057657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b8111156105c857600080fd5b8201836020820111156105da57600080fd5b803590602001918460018302840111600160201b831117156105fb57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611974945050505050565b604080519115158252519081900360200190f35b61063c6004803603606081101561066657600080fd5b810190602081018135600160201b81111561068057600080fd5b82018360208201111561069257600080fd5b803590602001918460018302840111600160201b831117156106b357600080fd5b919390929091602081019035600160201b8111156106d057600080fd5b8201836020820111156106e257600080fd5b803590602001918460018302840111600160201b8311171561070357600080fd5b919390929091602081019035600160201b81111561072057600080fd5b82018360208201111561073257600080fd5b803590602001918460018302840111600160201b8311171561075357600080fd5b509092509050611be8565b6103a16004803603606081101561077457600080fd5b810190602081018135600160201b81111561078e57600080fd5b8201836020820111156107a057600080fd5b803590602001918460018302840111600160201b831117156107c157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561081357600080fd5b82018360208201111561082557600080fd5b803590602001918460018302840111600160201b8311171561084657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561089857600080fd5b8201836020820111156108aa57600080fd5b803590602001918460018302840111600160201b831117156108cb57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611f68945050505050565b61063c6004803603608081101561092257600080fd5b810190602081018135600160201b81111561093c57600080fd5b82018360208201111561094e57600080fd5b803590602001918460018302840111600160201b8311171561096f57600080fd5b919390929091602081019035600160201b81111561098c57600080fd5b82018360208201111561099e57600080fd5b803590602001918460018302840111600160201b831117156109bf57600080fd5b919390929091602081019035600160201b8111156109dc57600080fd5b8201836020820111156109ee57600080fd5b803590602001918460018302840111600160201b83111715610a0f57600080fd5b9193509150356121c2565b61063c60048036036060811015610a3057600080fd5b810190602081018135600160201b811115610a4a57600080fd5b820183602082011115610a5c57600080fd5b803590602001918460018302840111600160201b83111715610a7d57600080fd5b919390929091602081019035600160201b811115610a9a57600080fd5b820183602082011115610aac57600080fd5b803590602001918460018302840111600160201b83111715610acd57600080fd5b919390929091602081019035600160201b811115610aea57600080fd5b820183602082011115610afc57600080fd5b803590602001918460018302840111600160201b83111715610b1d57600080fd5b50909250905061232e565b606080600080600080610bb28a8a8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8e018190048102820181019092528c815292508c91508b9081908401838280828437600092018290525060408051602081019091529081529250611974915050565b1515610c1c57898960008060008085858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506040805160208101909152908152939f50929d50959b509399509197509550610ea1945050505050565b6000610c918b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8f018190048102820181019092528d815292508d91508c90819084018382808284376000920191909152506126a392505050565b9050600181815481101515610ca257fe5b9060005260206000209060040201600001600182815481101515610cc257fe5b9060005260206000209060040201600101600183815481101515610ce257fe5b906000526020600020906004020160020154600184815481101515610d0357fe5b60009182526020909120600360049092020101546001805460ff9092169186908110610d2b57fe5b906000526020600020906004020160030160019054906101000a900460ff16600186815481101515610d5957fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff169290918891830182828015610dfe5780601f10610dd357610100808354040283529160200191610dfe565b820191906000526020600020905b815481529060010190602001808311610de157829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a945092508401905082828015610e8c5780601f10610e6157610100808354040283529160200191610e8c565b820191906000526020600020905b815481529060010190602001808311610e6f57829003601f168201915b50505050509450965096509650965096509650505b9499939850945094509450565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610efb57600080fd5b505afa158015610f0f573d6000803e3d6000fd5b505050506040513d6020811015610f2557600080fd5b50516001600160a01b03163314610f7a5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60088310610fd25760408051600160e51b62461bcd02815260206004820152601460248201527f696e76616c6964206163636573732076616c7565000000000000000000000000604482015290519081900360640190fd5b600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561101c578181015183820152602001611004565b50505050905090810190601f1680156110495780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561107c578181015183820152602001611064565b50505050905090810190601f1680156110a95780820380516001836020036101000a031916815260200191505b5094505050505060405160208183030381529060405280519060200120815260200190815260200160002054600014151561112e5760408051600160e51b62461bcd02815260206004820152601760248201527f726f6c652065786973747320666f7220746865206f7267000000000000000000604482015290519081900360640190fd5b60038054600101908190556040805160208082018381528951606084015289516002946000948c948c94938493830192608001918701908083838b5b8381101561118257818101518382015260200161116a565b50505050905090810190601f1680156111af5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156111e25781810151838201526020016111ca565b50505050905090810190601f16801561120f5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291815281516020928301208852878201989098529587016000908120989098555050845160c0810186528b81528085018b905294850189905250505084151560608301528315156080830152600160a083018190528054808201808355919094528251805191946004027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601926112b9928492909101906127b3565b5060208281015180516112d292600185019201906127b3565b5060408281015160028301556060808401516003909301805460808087015160a09788015160ff199093169615159690961761ff001916610100961515969096029590951762ff0000191662010000911515919091021790558151918201889052861515908201528415159181019190915281815287519181019190915286517fefa5bc1bedbee25b04b00855c15a0c180ecb4a2440d4d08296e49561655e2b1c92508791879187918791879190819060208083019160c08401918a019080838360005b838110156113ae578181015183820152602001611396565b50505050905090810190601f1680156113db5780820380516001836020036101000a031916815260200191505b50838103825287518152875160209182019189019080838360005b8381101561140e5781810151838201526020016113f6565b50505050905090810190601f16801561143b5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15050505050565b6001545b90565b60608060008060008060018781548110151561147357fe5b906000526020600020906004020160000160018881548110151561149357fe5b90600052602060002090600402016001016001898154811015156114b357fe5b90600052602060002090600402016002015460018a8154811015156114d457fe5b60009182526020909120600360049092020101546001805460ff909216918c9081106114fc57fe5b906000526020600020906004020160030160019054906101000a900460ff1660018c81548110151561152a57fe5b6000918252602091829020600491909102016003015486546040805160026101006001851615026000190190931692909204601f81018590048502830185019091528082526201000090920460ff1692909188918301828280156115cf5780601f106115a4576101008083540402835291602001916115cf565b820191906000526020600020905b8154815290600101906020018083116115b257829003601f168201915b5050885460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959b508a94509250840190508282801561165d5780601f106116325761010080835404028352916020019161165d565b820191906000526020600020905b81548152906001019060200180831161164057829003601f168201915b5050505050945095509550955095509550955091939550919395565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156116c657600080fd5b505afa1580156116da573d6000803e3d6000fd5b505050506040513d60208110156116f057600080fd5b50516001600160a01b031633146117455760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000858585856040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515156118315760408051600160e51b62461bcd02815260206004820152601360248201527f726f6c6520646f6573206e6f7420657869737400000000000000000000000000604482015290519081900360640190fd5b60006118a685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f890181900481028201810190925287815292508791508690819084018382808284376000920191909152506126a392505050565b905060006001828154811015156118b957fe5b906000526020600020906004020160030160026101000a81548160ff0219169083151502179055507f1196059dd83524bf989fd94bb65808c09dbea2ab791fb6bfa87a0e0aa64b2ea6858585856040518080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600083820152604051601f909101601f19169092018290039850909650505050505050a15050505050565b600080600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156119c15781810151838201526020016119a9565b50505050905090810190601f1680156119ee5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015611a21578181015183820152602001611a09565b50505050905090810190601f168015611a4e5780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611ac557611a8d85856126a3565b9050600181815481101515611a9e57fe5b906000526020600020906004020160030160029054906101000a900460ff16915050611be1565b600260008685604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611b0f578181015183820152602001611af7565b50505050905090810190601f168015611b3c5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015611b6f578181015183820152602001611b57565b50505050905090810190601f168015611b9c5780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611bdb57611a8d85846126a3565b60009150505b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015611c3757600080fd5b505afa158015611c4b573d6000803e3d6000fd5b505050506040513d6020811015611c6157600080fd5b50516001600160a01b03163314611cb65760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b611d5d87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061197492505050565b1515611d6b57506000611f5e565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f8201169050808301925050509650505050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611e8257611e7b88888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a91508990819084018382808284376000920191909152506126a392505050565b9050611ef8565b611ef588888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506126a392505050565b90505b6001805482908110611f0657fe5b906000526020600020906004020160030160029054906101000a900460ff168015611f5a57506001805482908110611f3a57fe5b906000526020600020906004020160030160019054906101000a900460ff165b9150505b9695505050505050565b600080600260008686604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611fb5578181015183820152602001611f9d565b50505050905090810190601f168015611fe25780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015612015578181015183820152602001611ffd565b50505050905090810190601f1680156120425780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415156120ac5761208185856126a3565b905060018181548110151561209257fe5b906000526020600020906004020160020154915050611be1565b600260008685604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156120f65781810151838201526020016120de565b50505050905090810190601f1680156121235780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b8381101561215657818101518382015260200161213e565b50505050905090810190601f1680156121835780820380516001836020036101000a031916815260200191505b50945050505050604051602081830303815290604052805190602001208152602001908152602001600020546000141515611bdb5761208185846126a3565b60008061226c89898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8d018190048102820181019092528b815292508b91508a908190840183828082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a9150899081908401838280828437600092019190915250611f6892505050565b90508060031415612281576001915050612323565b8260011480156122a65750806001148061229b5750806005145b806122a65750806006145b156122b5576001915050612323565b8260021480156122da575080600214806122cf5750806006145b806122da5750806007145b156122e9576001915050612323565b82600314801561230e575080600414806123035750806005145b8061230e5750806007145b1561231d576001915050612323565b60009150505b979650505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561237d57600080fd5b505afa158015612391573d6000803e3d6000fd5b505050506040513d60208110156123a757600080fd5b50516001600160a01b031633146123fc5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b6124a387878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8b01819004810282018101909252898152925089915088908190840183828082843760009201919091525050604080516020601f8a01819004810282018101909252888152925088915087908190840183828082843760009201919091525061197492505050565b15156124b157506000611f5e565b600060026000898989896040516020018080602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f82011690508083019250505096505050505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415156125c8576125c188888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8c018190048102820181019092528a815292508a91508990819084018382808284376000920191909152506126a392505050565b905061263e565b61263b88888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506126a392505050565b90505b600180548290811061264c57fe5b906000526020600020906004020160030160029054906101000a900460ff168015611f5a5750600180548290811061268057fe5b600091825260209091206004909102016003015460ff1698975050505050505050565b60006001600260008585604051602001808060200180602001838103835285818151815260200191508051906020019080838360005b838110156126f15781810151838201526020016126d9565b50505050905090810190601f16801561271e5780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b83811015612751578181015183820152602001612739565b50505050905090810190601f16801561277e5780820380516001836020036101000a031916815260200191505b509450505050506040516020818303038152906040528051906020012081526020019081526020016000205403905092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106127f457805160ff1916838001178555612821565b82800160010185558215612821579182015b82811115612821578251825591602001919060010190612806565b5061282d929150612831565b5090565b61145891905b8082111561282d576000815560010161283756fea165627a7a7230582075c8fd5504ba38b54f31bd0f3c28ac8ef88060c8e359bff58088d242f8cd256c0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/VoterManager.abi b/permission/v2/contract/gen/VoterManager.abi new file mode 100644 index 0000000000..10c875209b --- /dev/null +++ b/permission/v2/contract/gen/VoterManager.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_orgId","type":"string"}],"name":"getPendingOpDetails","outputs":[{"name":"","type":"string"},{"name":"","type":"string"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_vAccount","type":"address"}],"name":"addVoter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_orgId","type":"string"},{"name":"_vAccount","type":"address"}],"name":"deleteVoter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_authOrg","type":"string"},{"name":"_vAccount","type":"address"},{"name":"_pendingOp","type":"uint256"}],"name":"processVote","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_authOrg","type":"string"},{"name":"_orgId","type":"string"},{"name":"_enodeId","type":"string"},{"name":"_account","type":"address"},{"name":"_pendingOp","type":"uint256"}],"name":"addVotingItem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_permUpgradable","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_vAccount","type":"address"}],"name":"VoterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"},{"indexed":false,"name":"_vAccount","type":"address"}],"name":"VoterDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"}],"name":"VotingItemAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_orgId","type":"string"}],"name":"VoteProcessed","type":"event"}] \ No newline at end of file diff --git a/permission/v2/contract/gen/VoterManager.bin b/permission/v2/contract/gen/VoterManager.bin new file mode 100644 index 0000000000..9d9cde004b --- /dev/null +++ b/permission/v2/contract/gen/VoterManager.bin @@ -0,0 +1 @@ +6080604052600060035534801561001557600080fd5b50604051602080611fe48339810180604052602081101561003557600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055611f7d806100676000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063014e6acc1461005c5780635607395b146101c857806359cbd6fe14610241578063b0213864146102b8578063e98ac22d14610349575b600080fd5b6100ca6004803603602081101561007257600080fd5b810190602081018135600160201b81111561008c57600080fd5b82018360208201111561009e57600080fd5b803590602001918460018302840111600160201b831117156100bf57600080fd5b509092509050610466565b604051808060200180602001856001600160a01b03166001600160a01b03168152602001848152602001838103835287818151815260200191508051906020019080838360005b83811015610129578181015183820152602001610111565b50505050905090810190601f1680156101565780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838360005b83811015610189578181015183820152602001610171565b50505050905090810190601f1680156101b65780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b61023f600480360360408110156101de57600080fd5b810190602081018135600160201b8111156101f857600080fd5b82018360208201111561020a57600080fd5b803590602001918460018302840111600160201b8311171561022b57600080fd5b9193509150356001600160a01b0316610740565b005b61023f6004803603604081101561025757600080fd5b810190602081018135600160201b81111561027157600080fd5b82018360208201111561028357600080fd5b803590602001918460018302840111600160201b831117156102a457600080fd5b9193509150356001600160a01b0316610f06565b610335600480360360608110156102ce57600080fd5b810190602081018135600160201b8111156102e857600080fd5b8201836020820111156102fa57600080fd5b803590602001918460018302840111600160201b8311171561031b57600080fd5b91935091506001600160a01b0381351690602001356111e8565b604080519115158252519081900360200190f35b61023f600480360360a081101561035f57600080fd5b810190602081018135600160201b81111561037957600080fd5b82018360208201111561038b57600080fd5b803590602001918460018302840111600160201b831117156103ac57600080fd5b919390929091602081019035600160201b8111156103c957600080fd5b8201836020820111156103db57600080fd5b803590602001918460018302840111600160201b831117156103fc57600080fd5b919390929091602081019035600160201b81111561041957600080fd5b82018360208201111561042b57600080fd5b803590602001918460018302840111600160201b8311171561044c57600080fd5b91935091506001600160a01b0381351690602001356116f8565b6060806000806000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b1580156104b957600080fd5b505afa1580156104cd573d6000803e3d6000fd5b505050506040513d60208110156104e357600080fd5b50516001600160a01b031633146105385760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600061057987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561058a57fe5b90600052602060002090600b02016004016000016001828154811015156105ad57fe5b90600052602060002090600b02016004016001016001838154811015156105d057fe5b600091825260209091206006600b909202010154600180546001600160a01b0390921691859081106105fe57fe5b60009182526020918290206007600b909202010154845460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815291928691908301828280156106995780601f1061066e57610100808354040283529160200191610699565b820191906000526020600020905b81548152906001019060200180831161067c57829003601f168201915b5050865460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152959950889450925084019050828280156107275780601f106106fc57610100808354040283529160200191610727565b820191906000526020600020905b81548152906001019060200180831161070a57829003601f168201915b5050505050925094509450945094505092959194509250565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561078d57600080fd5b505afa1580156107a1573d6000803e3d6000fd5b505050506040513d60208110156107b757600080fd5b50516001600160a01b0316331461080c5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60026000848460405160200180806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505093505050506040516020818303038152906040528051906020012081526020019081526020016000205460001415610b78576003805460010190819055604080516020808201908152918101859052600291600091879187918190606001848480828437600081840152601f19601f8201169050808301925050509350505050604051602081830303815290604052805190602001208152602001908152602001600020819055506000600180548091906001016109069190611cdd565b9050838360018381548110151561091957fe5b6000918252602090912061093393600b9092020191611d0e565b506001808281548110151561094457fe5b90600052602060002090600b0201600101819055506001808281548110151561096957fe5b90600052602060002090600b020160020181905550600060018281548110151561098f57fe5b90600052602060002090600b020160030181905550604051806020016040528060008152506001828154811015156109c357fe5b90600052602060002090600b020160040160000190805190602001906109ea929190611d8c565b506040805160208101909152600081526001805483908110610a0857fe5b90600052602060002090600b02016004016001019080519060200190610a2f929190611d8c565b506000600182815481101515610a4157fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b0393909316929092179091556001805483908110610a8157fe5b600091825260209091206007600b9092020101556001805482908110610aa357fe5b90600052602060002090600b020160010154600182815481101515610ac457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610afb57fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b969096029093016008018054938401815586529290942093519301805492516001600160a01b03199093169390911692909217600160a01b60ff021916600160a01b9115159190910217905550610e90565b6000610bb984848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050600181815481101515610bca57fe5b600091825260208083206001600160a01b03861684526009600b9093020191909101905260409020541515610d2c576001805482908110610c0757fe5b600091825260209091206001600b909202018101805482019055805482908110610c2d57fe5b90600052602060002090600b020160010154600182815481101515610c4e57fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020556001805482908110610c8557fe5b60009182526020808320604080518082019091526001600160a01b0387811682526001828501818152600b96909602909301600801805480850182559087529390952090519201805493516001600160a01b03199094169290941691909117600160a01b60ff021916600160a01b9215159290920291909117909155805482908110610d0d57fe5b600091825260209091206002600b909202010180546001019055610e8e565b6000610d6f85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250879250611ba5915050565b9050600182815481101515610d8057fe5b90600052602060002090600b020160080181815481101515610d9e57fe5b600091825260209091200154600160a01b900460ff16151560011415610e0e5760408051600160e51b62461bcd02815260206004820152600f60248201527f616c7265616479206120766f7465720000000000000000000000000000000000604482015290519081900360640190fd5b60018083815481101515610e1e57fe5b90600052602060002090600b020160080182815481101515610e3c57fe5b60009182526020909120018054911515600160a01b02600160a01b60ff02199092169190911790556001805483908110610e7257fe5b600091825260209091206002600b909202010180546001019055505b505b604080516001600160a01b03831660208201528181529081018390527f424f3ad05c61ea35cad66f22b70b1fad7250d8229921238078c401db36d34574908490849084908060608101858580828437600083820152604051601f909101601f1916909201829003965090945050505050a1505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b158015610f5357600080fd5b505afa158015610f67573d6000803e3d6000fd5b505050506040513d6020811015610f7d57600080fd5b50516001600160a01b03163314610fd25760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250849250611016915083905082611bf7565b15156001146110645760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b60006110a586868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060006110ea87878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250899250611ba5915050565b90506001828154811015156110fb57fe5b6000918252602082206002600b90920201018054600019019055600180548490811061112357fe5b90600052602060002090600b02016008018281548110151561114157fe5b9060005260206000200160000160146101000a81548160ff0219169083151502179055507f654cd85d9b2abaf3affef0a047625d088e6e4d0448935c9b5016b5f5aa0ca3b68787876040518080602001836001600160a01b03166001600160a01b031681526020018281038252858582818152602001925080828437600083820152604051601f909101601f1916909201829003965090945050505050a150505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561123757600080fd5b505afa15801561124b573d6000803e3d6000fd5b505050506040513d602081101561126157600080fd5b50516001600160a01b031633146112b65760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508692506112fa915083905082611bf7565b15156001146113485760408051600160e51b62461bcd02815260206004820152600f6024820152600160891b6e36bab9ba1031329030903b37ba32b902604482015290519081900360640190fd5b61138987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250889250611ca7915050565b15156001146113e25760408051600160e51b62461bcd02815260206004820152601260248201527f6e6f7468696e6720746f20617070726f76650000000000000000000000000000604482015290519081900360640190fd5b600061142388888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b905060018181548110151561143457fe5b60009182526020808320848452600a600b9093020191909101815260408083206001600160a01b038a16845290915290205460ff161515600114156114c35760408051600160e51b62461bcd02815260206004820152601260248201527f63616e6e6f7420646f75626c6520766f74650000000000000000000000000000604482015290519081900360640190fd5b60018054829081106114d157fe5b600091825260209091206003600b90920201018054600190810190915580548190839081106114fc57fe5b60009182526020808320858452600b92909202909101600a01815260408083206001600160a01b038b168452825291829020805460ff19169315159390931790925580518281529182018990527f87999b54e45aa02834a1265e356d7bcdceb72b8cbb4396ebaeba32a103b43508918a918a919081908101848480828437600083820152604051601f909101601f19169092018290039550909350505050a160026001828154811015156115ac57fe5b90600052602060002090600b0201600201548115156115c757fe5b046001828154811015156115d757fe5b90600052602060002090600b02016003015411156116e857604080516020810190915260008152600180548390811061160c57fe5b90600052602060002090600b02016004016000019080519060200190611633929190611d8c565b50604080516020810190915260008152600180548390811061165157fe5b90600052602060002090600b02016004016001019080519060200190611678929190611d8c565b50600060018281548110151561168a57fe5b600091825260208220600b919091020160060180546001600160a01b0319166001600160a01b03939093169290921790915560018054839081106116ca57fe5b600091825260209091206007600b90920201015550600192506116ee565b60009350505b5050949350505050565b6000809054906101000a90046001600160a01b03166001600160a01b0316630e32cf906040518163ffffffff1660e01b815260040160206040518083038186803b15801561174557600080fd5b505afa158015611759573d6000803e3d6000fd5b505050506040513d602081101561176f57600080fd5b50516001600160a01b031633146117c45760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b61180388888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509250611ca7915050565b151561184357604051600160e51b62461bcd028152600401808060200182810382526034815260200180611f1e6034913960400191505060405180910390fd5b600061188489898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611afd92505050565b9050868660018381548110151561189757fe5b90600052602060002090600b020160040160000191906118b8929190611d0e565b5084846001838154811015156118ca57fe5b90600052602060002090600b020160040160010191906118eb929190611d0e565b50826001828154811015156118fc57fe5b90600052602060002090600b020160040160020160006101000a8154816001600160a01b0302191690836001600160a01b031602179055508160018281548110151561194457fe5b6000918252602082206007600b9092020101919091555b600180548390811061196957fe5b90600052602060002090600b020160080180549050811015611a6b57600180548390811061199357fe5b90600052602060002090600b0201600801818154811015156119b157fe5b600091825260209091200154600160a01b900460ff1615611a635760006001838154811015156119dd57fe5b90600052602060002090600b0201600a0160008481526020019081526020016000206000600185815481101515611a1057fe5b90600052602060002090600b020160080184815481101515611a2e57fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff19169115159190911790555b60010161195b565b506000600182815481101515611a7d57fe5b90600052602060002090600b0201600301819055507f5bfaebb5931145594f63236d2a59314c4dc6035b65d0ca4cee9c7298e2f06ca3898960405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a1505050505050505050565b6000600160026000846040516020018080602001828103825283818151815260200191508051906020019080838360005b83811015611b46578181015183820152602001611b2e565b50505050905090810190601f168015611b735780820380516001836020036101000a031916815260200191505b509250505060405160208183030381529060405280519060200120815260200190815260200160002054039050919050565b600080611bb184611afd565b905060018082815481101515611bc357fe5b600091825260208083206001600160a01b03881684526009600b909302019190910190526040902054039150505b92915050565b600080611c0384611afd565b9050600181815481101515611c1457fe5b600091825260208083206001600160a01b03871684526009600b9093020191909101905260409020541515611c4d576000915050611bf1565b6000611c598585611ba5565b9050600182815481101515611c6a57fe5b90600052602060002090600b020160080181815481101515611c8857fe5b600091825260209091200154600160a01b900460ff1695945050505050565b6000816001611cb585611afd565b81548110611cbf57fe5b90600052602060002090600b02016004016003015414905092915050565b815481835581811115611d0957600b0281600b028360005260206000209182019101611d099190611dfa565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611d4f5782800160ff19823516178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578235825591602001919060010190611d61565b50611d88929150611e7f565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611dcd57805160ff1916838001178555611d7c565b82800160010185558215611d7c579182015b82811115611d7c578251825591602001919060010190611ddf565b611e7c91905b80821115611d88576000611e148282611e99565b60006001830181905560028301819055600383018190556004830190611e3a8282611e99565b611e48600183016000611e99565b506002810180546001600160a01b031916905560006003909101819055611e73906008840190611ee0565b50600b01611e00565b90565b611e7c91905b80821115611d885760008155600101611e85565b50805460018160011615610100020316600290046000825580601f10611ebf5750611edd565b601f016020900490600052602060002090810190611edd9190611e7f565b50565b5080546000825590600052602060002090810190611edd9190611e7c91905b80821115611d885780546001600160a81b0319168155600101611eff56fe6974656d732070656e64696e6720666f7220617070726f76616c2e206e6577206974656d2063616e6e6f74206265206164646564a165627a7a723058207dc9a68c6931494f043f7420dfa8288733d9a3a676ed30b4ac8e9cc704bd928b0029 \ No newline at end of file diff --git a/permission/v2/contract/gen/gen.go b/permission/v2/contract/gen/gen.go new file mode 100644 index 0000000000..1c622e2a16 --- /dev/null +++ b/permission/v2/contract/gen/gen.go @@ -0,0 +1,27 @@ +// Quorum +// +// this is to generate go binding for smart contracts used in permissioning +// +// Require: +// 1. solc 0.5.4 +// 2. abigen (make all from root) + +//go:generate solc --abi --bin -o . --overwrite ../AccountManager.sol +//go:generate solc --abi --bin -o . --overwrite ../NodeManager.sol +//go:generate solc --abi --bin -o . --overwrite ../OrgManager.sol +//go:generate solc --abi --bin -o . --overwrite ../PermissionsImplementation.sol +//go:generate solc --abi --bin -o . --overwrite ../PermissionsInterface.sol +//go:generate solc --abi --bin -o . --overwrite ../PermissionsUpgradable.sol +//go:generate solc --abi --bin -o . --overwrite ../RoleManager.sol +//go:generate solc --abi --bin -o . --overwrite ../VoterManager.sol + +//go:generate abigen -pkg bind -abi ./AccountManager.abi -bin ./AccountManager.bin -type AcctManager -out ../../bind/accounts.go +//go:generate abigen -pkg bind -abi ./NodeManager.abi -bin ./NodeManager.bin -type NodeManager -out ../../bind/nodes.go +//go:generate abigen -pkg bind -abi ./OrgManager.abi -bin ./OrgManager.bin -type OrgManager -out ../../bind/org.go +//go:generate abigen -pkg bind -abi ./PermissionsImplementation.abi -bin ./PermissionsImplementation.bin -type PermImpl -out ../../bind/permission_impl.go +//go:generate abigen -pkg bind -abi ./PermissionsInterface.abi -bin ./PermissionsInterface.bin -type PermInterface -out ../../bind/permission_interface.go +//go:generate abigen -pkg bind -abi ./PermissionsUpgradable.abi -bin ./PermissionsUpgradable.bin -type permUpgr -out ../../bind/permission_upgr.go +//go:generate abigen -pkg bind -abi ./RoleManager.abi -bin ./RoleManager.bin -type RoleManager -out ../../bind/roles.go +//go:generate abigen -pkg bind -abi ./VoterManager.abi -bin ./VoterManager.bin -type VoterManager -out ../../bind/voter.go + +package gen diff --git a/plugin/account/connector.go b/plugin/account/connector.go new file mode 100644 index 0000000000..83fe2ddd8b --- /dev/null +++ b/plugin/account/connector.go @@ -0,0 +1,26 @@ +package account + +import ( + "context" + + iplugin "github.com/ethereum/go-ethereum/internal/plugin" + "github.com/hashicorp/go-plugin" + "github.com/jpmorganchase/quorum-account-plugin-sdk-go/proto" + "google.golang.org/grpc" +) + +const ConnectorName = "account" + +type PluginConnector struct { + plugin.Plugin +} + +func (*PluginConnector) GRPCServer(_ *plugin.GRPCBroker, _ *grpc.Server) error { + return iplugin.ErrNotSupported +} + +func (*PluginConnector) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, cc *grpc.ClientConn) (interface{}, error) { + return &service{ + client: proto.NewAccountServiceClient(cc), + }, nil +} diff --git a/plugin/account/creator.go b/plugin/account/creator.go new file mode 100644 index 0000000000..db3c4c88e8 --- /dev/null +++ b/plugin/account/creator.go @@ -0,0 +1,25 @@ +package account + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts" +) + +type creator struct { + service Service +} + +// NewCreator creates an implementation of CreatorService that simply acts as a delegate to service. This method +// exists to allow for only the CreatorService methods to be exposed as APIs with the plugin delegate API framework. +func NewCreator(service Service) CreatorService { + return &creator{service: service} +} + +func (a *creator) NewAccount(ctx context.Context, newAccountConfig interface{}) (accounts.Account, error) { + return a.service.NewAccount(ctx, newAccountConfig) +} + +func (a *creator) ImportRawKey(ctx context.Context, rawKey string, newAccountConfig interface{}) (accounts.Account, error) { + return a.service.ImportRawKey(ctx, rawKey, newAccountConfig) +} diff --git a/plugin/account/gateway.go b/plugin/account/gateway.go new file mode 100644 index 0000000000..1540cb22b3 --- /dev/null +++ b/plugin/account/gateway.go @@ -0,0 +1,213 @@ +package account + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/jpmorganchase/quorum-account-plugin-sdk-go/proto" +) + +type service struct { + client proto.AccountServiceClient + mu sync.Mutex + isStreaming bool +} + +func (g *service) Status(ctx context.Context) (string, error) { + resp, err := g.client.Status(ctx, &proto.StatusRequest{}) + if err != nil { + return "", err + } + if resp == nil { + return "", errors.New("empty response from plugin") + } + return resp.Status, err +} + +func (g *service) Open(ctx context.Context, passphrase string) error { + _, err := g.client.Open(ctx, &proto.OpenRequest{Passphrase: passphrase}) + return err +} + +func (g *service) Close(ctx context.Context) error { + _, err := g.client.Close(ctx, &proto.CloseRequest{}) + return err +} + +func (g *service) Accounts(ctx context.Context) []accounts.Account { + resp, err := g.client.Accounts(ctx, &proto.AccountsRequest{}) + if err != nil { + log.Error("unable to get accounts from plugin account store", "err", err) + return []accounts.Account{} + } + if resp == nil { + log.Error("empty response from plugin") + return []accounts.Account{} + } + + return asAccounts(resp.Accounts) +} + +func (g *service) Contains(ctx context.Context, account accounts.Account) bool { + resp, err := g.client.Contains(ctx, &proto.ContainsRequest{Address: account.Address.Bytes()}) + if err != nil { + log.Error("unable to check contents of plugin account store", "err", err) + } + if resp == nil { + log.Error("empty response from plugin") + return false + } + + return resp.IsContained +} + +func (g *service) Sign(ctx context.Context, account accounts.Account, toSign []byte) ([]byte, error) { + resp, err := g.client.Sign(ctx, &proto.SignRequest{ + Address: account.Address.Bytes(), + ToSign: toSign, + }) + if err != nil { + return nil, err + } + if resp == nil { + return nil, errors.New("empty response from plugin") + } + + return resp.Sig, nil +} + +func (g *service) UnlockAndSign(ctx context.Context, account accounts.Account, toSign []byte, passphrase string) ([]byte, error) { + resp, err := g.client.UnlockAndSign(ctx, &proto.UnlockAndSignRequest{ + Address: account.Address.Bytes(), + ToSign: toSign, + Passphrase: passphrase, + }) + if err != nil { + return nil, err + } + if resp == nil { + return nil, errors.New("empty response from plugin") + } + + return resp.Sig, nil +} + +func (g *service) TimedUnlock(ctx context.Context, account accounts.Account, password string, duration time.Duration) error { + _, err := g.client.TimedUnlock(ctx, &proto.TimedUnlockRequest{Address: account.Address.Bytes(), Password: password, Duration: duration.Nanoseconds()}) + return err +} + +func (g *service) Lock(ctx context.Context, account accounts.Account) error { + _, err := g.client.Lock(ctx, &proto.LockRequest{Address: account.Address.Bytes()}) + return err +} + +func (g *service) NewAccount(ctx context.Context, newAccountConfig interface{}) (accounts.Account, error) { + byt, err := json.Marshal(newAccountConfig) + if err != nil { + return accounts.Account{}, err + } + + req := &proto.NewAccountRequest{ + NewAccountConfig: byt, + } + resp, err := g.client.NewAccount(ctx, req) + if err != nil { + return accounts.Account{}, err + } + + acct, err := asAccount(resp.Account) + if err != nil { + return accounts.Account{}, err + } + + return acct, nil +} + +func (g *service) ImportRawKey(ctx context.Context, rawKey string, newAccountConfig interface{}) (accounts.Account, error) { + byt, err := json.Marshal(newAccountConfig) + if err != nil { + return accounts.Account{}, err + } + // validate the rawKey + _, err = crypto.HexToECDSA(rawKey) + if err != nil { + return accounts.Account{}, err + } + req := &proto.ImportRawKeyRequest{ + RawKey: rawKey, + NewAccountConfig: byt, + } + resp, err := g.client.ImportRawKey(ctx, req) + if err != nil { + return accounts.Account{}, err + } + + acct, err := asAccount(resp.Account) + if err != nil { + return accounts.Account{}, err + } + + return acct, nil +} + +func asAccounts(pAccts []*proto.Account) []accounts.Account { + accts := make([]accounts.Account, 0, len(pAccts)) + + for i, pAcct := range pAccts { + acct, err := asAccount(pAcct) + if err != nil { + log.Error("unable to parse account from plugin account store", "index", i, "err", err) + continue + } + + accts = append(accts, acct) + } + + return accts +} + +func asAccount(pAcct *proto.Account) (accounts.Account, error) { + addr := strings.TrimSpace(common.Bytes2Hex(pAcct.Address)) + + if !common.IsHexAddress(addr) { + return accounts.Account{}, fmt.Errorf("invalid hex address: %v", addr) + } + + url, err := ToUrl(pAcct.Url) + if err != nil { + return accounts.Account{}, err + } + + acct := accounts.Account{ + Address: common.HexToAddress(addr), + URL: url, + } + + return acct, nil +} + +func ToUrl(strUrl string) (accounts.URL, error) { + if strUrl == "" { + return accounts.URL{}, nil + } + + //to parse a string url as an accounts.URL it must first be in json format + toParse := fmt.Sprintf("\"%v\"", strUrl) + + var url accounts.URL + if err := url.UnmarshalJSON([]byte(toParse)); err != nil { + return accounts.URL{}, err + } + + return url, nil +} diff --git a/plugin/account/gateway_test.go b/plugin/account/gateway_test.go new file mode 100644 index 0000000000..92e68b1f8a --- /dev/null +++ b/plugin/account/gateway_test.go @@ -0,0 +1,338 @@ +package account + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/plugin/account/internal/testutils" + "github.com/golang/mock/gomock" + "github.com/jpmorganchase/quorum-account-plugin-sdk-go/mock_proto" + "github.com/jpmorganchase/quorum-account-plugin-sdk-go/proto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + scheme = "scheme" + + acct1 = accounts.Account{ + Address: common.HexToAddress("0x4d6d744b6da435b5bbdde2526dc20e9a41cb72e5"), + URL: accounts.URL{Scheme: scheme, Path: "acctUri1"}, + } + + acct2 = accounts.Account{ + Address: common.HexToAddress("0x2332f90a329c2c55ba120b1449d36a144d1f9fe4"), + URL: accounts.URL{Scheme: scheme, Path: "acctUri2"}, + } +) + +func TestPluginGateway_Status(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.StatusResponse{Status: "some status"} + + wantReq := &proto.StatusRequest{} + wantStatus := resp.Status + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Status(gomock.Any(), testutils.StatusRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + got, err := g.Status(context.Background()) + + assert.NoError(t, err) + assert.Equal(t, wantStatus, got) +} + +func TestPluginGateway_Open(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.OpenResponse{} + + wantReq := &proto.OpenRequest{Passphrase: "pwd"} + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Open(gomock.Any(), testutils.OpenRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + err := g.Open(context.Background(), "pwd") + + assert.NoError(t, err) +} + +func TestPluginGateway_Close(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.CloseResponse{} + + wantReq := &proto.CloseRequest{} + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Close(gomock.Any(), testutils.CloseRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + err := g.Close(context.Background()) + + assert.NoError(t, err) +} + +func TestPluginGateway_Accounts(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.AccountsResponse{ + Accounts: []*proto.Account{ + {Address: acct1.Address.Bytes(), Url: acct1.URL.String()}, + {Address: acct2.Address.Bytes(), Url: acct2.URL.String()}, + }, + } + + wantReq := &proto.AccountsRequest{} + wantAccts := []accounts.Account{acct1, acct2} + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Accounts(gomock.Any(), testutils.AccountsRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + got := g.Accounts(context.Background()) + + assert.Equal(t, wantAccts, got) +} + +func TestPluginGateway_Contains(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.ContainsResponse{IsContained: true} + + wantReq := &proto.ContainsRequest{ + Address: acct1.Address.Bytes(), + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Contains(gomock.Any(), testutils.ContainsRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + got := g.Contains(context.Background(), acct1) + + assert.True(t, got) +} + +func TestPluginGateway_Sign(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + want := []byte("signed data") + resp := &proto.SignResponse{Sig: want} + + toSign := []byte("to sign") + wantReq := &proto.SignRequest{ + Address: acct1.Address.Bytes(), + ToSign: toSign, + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Sign(gomock.Any(), testutils.SignRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + got, err := g.Sign(context.Background(), acct1, toSign) + + assert.NoError(t, err) + assert.Equal(t, want, got) +} + +func TestPluginGateway_UnlockAndSign(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + want := []byte("signed data") + resp := &proto.SignResponse{Sig: want} + + toSign := []byte("to sign") + wantReq := &proto.UnlockAndSignRequest{ + Address: acct1.Address.Bytes(), + ToSign: toSign, + Passphrase: "pwd", + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + UnlockAndSign(gomock.Any(), testutils.UnlockAndSignRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + got, err := g.UnlockAndSign(context.Background(), acct1, toSign, "pwd") + + assert.NoError(t, err) + assert.Equal(t, want, got) +} + +func TestPluginGateway_TimedUnlock(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.TimedUnlockResponse{} + + pwd := "pwd" + + wantReq := &proto.TimedUnlockRequest{ + Address: acct1.Address.Bytes(), + Password: pwd, + Duration: int64(1), + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + TimedUnlock(gomock.Any(), testutils.TimedUnlockRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + err := g.TimedUnlock(context.Background(), acct1, pwd, time.Nanosecond) + + assert.NoError(t, err) +} + +func TestPluginGateway_Lock(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.LockResponse{} + + wantReq := &proto.LockRequest{ + Address: acct1.Address.Bytes(), + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + Lock(gomock.Any(), testutils.LockRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + err := g.Lock(context.Background(), acct1) + + assert.NoError(t, err) +} + +func TestPluginGateway_NewAccount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.NewAccountResponse{ + Account: &proto.Account{ + Address: acct1.Address.Bytes(), + Url: acct1.URL.String(), + }, + } + + newAccountConfig := []byte("newacctconfig") + + b, err := json.Marshal(newAccountConfig) + require.NoError(t, err) + wantReq := &proto.NewAccountRequest{ + NewAccountConfig: b, + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + NewAccount(gomock.Any(), testutils.NewAccountRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + gotAcct, err := g.NewAccount(context.Background(), newAccountConfig) + + assert.Equal(t, acct1, gotAcct) + assert.NoError(t, err) +} + +func TestPluginGateway_ImportRawKey(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + resp := &proto.ImportRawKeyResponse{ + Account: &proto.Account{ + Address: acct1.Address.Bytes(), + Url: acct1.URL.String(), + }, + } + + newAccountConfig := []byte("newacctconfig") + var rawKey = "1fe8f1ad4053326db20529257ac9401f2e6c769ef1d736b8c2f5aba5f787c72b" + + b, err := json.Marshal(newAccountConfig) + require.NoError(t, err) + wantReq := &proto.ImportRawKeyRequest{ + RawKey: rawKey, + NewAccountConfig: b, + } + + mockClient := mock_proto.NewMockAccountServiceClient(ctrl) + mockClient. + EXPECT(). + ImportRawKey(gomock.Any(), testutils.ImportRawKeyRequestMatcher{R: wantReq}). + Return(resp, nil) + + g := &service{client: mockClient} + gotAcct, err := g.ImportRawKey(context.Background(), rawKey, newAccountConfig) + + assert.Equal(t, acct1, gotAcct) + assert.NoError(t, err) +} + +func TestPluginGateway_ImportRawKey_InvalidRawKey(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + newAccountConfig := []byte("newacctconfig") + var rawKey = "aaaaaa" + + g := &service{} + _, err := g.ImportRawKey(context.Background(), rawKey, newAccountConfig) + + require.EqualError(t, err, "invalid length, need 256 bits") +} + +func Test_ToUrl(t *testing.T) { + strUrl := "http://myurl:8000" + want := accounts.URL{ + Scheme: "http", + Path: "myurl:8000", + } + got, err := ToUrl(strUrl) + require.NoError(t, err) + require.Equal(t, want, got) +} + +func Test_ToUrl_Invalid(t *testing.T) { + strUrl := "://noscheme:8000" + _, err := ToUrl(strUrl) + require.Error(t, err) +} diff --git a/plugin/account/internal/testutils/matchers.go b/plugin/account/internal/testutils/matchers.go new file mode 100644 index 0000000000..babc4ef719 --- /dev/null +++ b/plugin/account/internal/testutils/matchers.go @@ -0,0 +1,184 @@ +package testutils + +import ( + "fmt" + + protobuf "github.com/golang/protobuf/proto" + "github.com/jpmorganchase/quorum-account-plugin-sdk-go/proto" +) + +type StatusRequestMatcher struct { + R *proto.StatusRequest +} + +func (m StatusRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.StatusRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m StatusRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type OpenRequestMatcher struct { + R *proto.OpenRequest +} + +func (m OpenRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.OpenRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m OpenRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type CloseRequestMatcher struct { + R *proto.CloseRequest +} + +func (m CloseRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.CloseRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m CloseRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type AccountsRequestMatcher struct { + R *proto.AccountsRequest +} + +func (m AccountsRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.AccountsRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m AccountsRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type ContainsRequestMatcher struct { + R *proto.ContainsRequest +} + +func (m ContainsRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.ContainsRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m ContainsRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type SignRequestMatcher struct { + R *proto.SignRequest +} + +func (m SignRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.SignRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m SignRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type UnlockAndSignRequestMatcher struct { + R *proto.UnlockAndSignRequest +} + +func (m UnlockAndSignRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.UnlockAndSignRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m UnlockAndSignRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type TimedUnlockRequestMatcher struct { + R *proto.TimedUnlockRequest +} + +func (m TimedUnlockRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.TimedUnlockRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m TimedUnlockRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type LockRequestMatcher struct { + R *proto.LockRequest +} + +func (m LockRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.LockRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m LockRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type NewAccountRequestMatcher struct { + R *proto.NewAccountRequest +} + +func (m NewAccountRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.NewAccountRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m NewAccountRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} + +type ImportRawKeyRequestMatcher struct { + R *proto.ImportRawKeyRequest +} + +func (m ImportRawKeyRequestMatcher) Matches(x interface{}) bool { + r, ok := x.(*proto.ImportRawKeyRequest) + if !ok { + return false + } + return protobuf.Equal(m.R, r) +} + +func (m ImportRawKeyRequestMatcher) String() string { + return fmt.Sprintf("is %v", m.R) +} diff --git a/plugin/account/reloadableimpl.go b/plugin/account/reloadableimpl.go new file mode 100644 index 0000000000..f7c150b3e4 --- /dev/null +++ b/plugin/account/reloadableimpl.go @@ -0,0 +1,105 @@ +package account + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/log" +) + +type DispenseFunc func() (Service, error) + +type ReloadableService struct { + DispenseFunc DispenseFunc +} + +func (am *ReloadableService) Status(ctx context.Context) (string, error) { + s, err := am.DispenseFunc() + if err != nil { + return "", err + } + return s.Status(ctx) +} + +func (am *ReloadableService) Open(ctx context.Context, passphrase string) error { + s, err := am.DispenseFunc() + if err != nil { + return err + } + return s.Open(ctx, passphrase) +} + +func (am *ReloadableService) Close(ctx context.Context) error { + s, err := am.DispenseFunc() + if err != nil { + return err + } + return s.Close(ctx) +} + +func (am *ReloadableService) Accounts(ctx context.Context) []accounts.Account { + s, err := am.DispenseFunc() + if err != nil { + log.Error("unable to dispense account plugin", "err", err) + return []accounts.Account{} + } + return s.Accounts(ctx) +} + +func (am *ReloadableService) Contains(ctx context.Context, account accounts.Account) bool { + s, err := am.DispenseFunc() + if err != nil { + log.Error("unable to dispense account plugin", "err", err) + return false + } + return s.Contains(ctx, account) +} + +func (am *ReloadableService) Sign(ctx context.Context, account accounts.Account, toSign []byte) ([]byte, error) { + s, err := am.DispenseFunc() + if err != nil { + return nil, err + } + return s.Sign(ctx, account, toSign) +} + +func (am *ReloadableService) UnlockAndSign(ctx context.Context, account accounts.Account, toSign []byte, passphrase string) ([]byte, error) { + s, err := am.DispenseFunc() + if err != nil { + return nil, err + } + return s.UnlockAndSign(ctx, account, toSign, passphrase) +} + +func (am *ReloadableService) TimedUnlock(ctx context.Context, account accounts.Account, password string, duration time.Duration) error { + s, err := am.DispenseFunc() + if err != nil { + return err + } + return s.TimedUnlock(ctx, account, password, duration) +} + +func (am *ReloadableService) Lock(ctx context.Context, account accounts.Account) error { + s, err := am.DispenseFunc() + if err != nil { + return err + } + return s.Lock(ctx, account) +} + +func (am *ReloadableService) NewAccount(ctx context.Context, newAccountConfig interface{}) (accounts.Account, error) { + s, err := am.DispenseFunc() + if err != nil { + return accounts.Account{}, err + } + return s.NewAccount(ctx, newAccountConfig) +} + +func (am *ReloadableService) ImportRawKey(ctx context.Context, rawKey string, newAccountConfig interface{}) (accounts.Account, error) { + s, err := am.DispenseFunc() + if err != nil { + return accounts.Account{}, err + } + return s.ImportRawKey(ctx, rawKey, newAccountConfig) +} diff --git a/plugin/account/service.go b/plugin/account/service.go new file mode 100644 index 0000000000..a05a69df85 --- /dev/null +++ b/plugin/account/service.go @@ -0,0 +1,26 @@ +package account + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/accounts" +) + +type Service interface { + Status(ctx context.Context) (string, error) + Open(ctx context.Context, passphrase string) error + Close(ctx context.Context) error + Accounts(ctx context.Context) []accounts.Account + Contains(ctx context.Context, account accounts.Account) bool + Sign(ctx context.Context, account accounts.Account, toSign []byte) ([]byte, error) + UnlockAndSign(ctx context.Context, account accounts.Account, toSign []byte, passphrase string) ([]byte, error) + TimedUnlock(ctx context.Context, account accounts.Account, password string, duration time.Duration) error + Lock(ctx context.Context, account accounts.Account) error + CreatorService +} + +type CreatorService interface { + NewAccount(ctx context.Context, newAccountConfig interface{}) (accounts.Account, error) + ImportRawKey(ctx context.Context, rawKey string, newAccountConfig interface{}) (accounts.Account, error) +} diff --git a/plugin/api.go b/plugin/api.go new file mode 100644 index 0000000000..38c65794e5 --- /dev/null +++ b/plugin/api.go @@ -0,0 +1,15 @@ +package plugin + +type PluginManagerAPI struct { + pm *PluginManager +} + +func NewPluginManagerAPI(pm *PluginManager) *PluginManagerAPI { + return &PluginManagerAPI{ + pm: pm, + } +} + +func (pmapi *PluginManagerAPI) ReloadPlugin(name PluginInterfaceName) (bool, error) { + return pmapi.pm.Reload(name) +} diff --git a/plugin/base.go b/plugin/base.go new file mode 100644 index 0000000000..90aef1c69f --- /dev/null +++ b/plugin/base.go @@ -0,0 +1,291 @@ +package plugin + +import ( + "context" + "fmt" + "io" + slog "log" + "os" + "os/exec" + "path" + "path/filepath" + "reflect" + "time" + + "github.com/ethereum/go-ethereum/common" + iplugin "github.com/ethereum/go-ethereum/internal/plugin" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/plugin/initializer" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +type managedPlugin interface { + Start() error + Stop() error + + Info() (PluginInterfaceName, interface{}) +} + +// Plugin-meta.json +type MetaData struct { + Version string `json:"version"` + Os string `json:"os"` + Arch string `json:"arch"` + EntryPoint string `json:"entrypoint"` + Parameters []string `json:"parameters,omitempty"` +} + +type basePlugin struct { + pm *PluginManager + pluginInterface PluginInterfaceName // plugin provider name + pluginDefinition *PluginDefinition + client *plugin.Client + gateways plugin.PluginSet // gateways to invoke RPC API implementation of interfaces supported by this plugin + pluginWorkspace string // plugin workspace + commands []string // plugin executable commands + logger log.Logger +} + +var basePluginPointerType = reflect.TypeOf(&basePlugin{}) + +func newBasePlugin(pm *PluginManager, pluginInterface PluginInterfaceName, pluginDefinition PluginDefinition, gateways plugin.PluginSet) (*basePlugin, error) { + gateways[initializer.ConnectorName] = &initializer.PluginConnector{} + + // build basePlugin + return &basePlugin{ + pm: pm, + pluginInterface: pluginInterface, + logger: log.New("provider", pluginInterface, "plugin", pluginDefinition.Name, "version", pluginDefinition.Version), + pluginDefinition: &pluginDefinition, + gateways: gateways, + }, nil + +} + +// metadata.Command must be populated correctly here +func (bp *basePlugin) load() error { + // Get plugin distribution path + pluginDistFilePath, err := bp.pm.downloader.Download(bp.pluginDefinition) + if err != nil { + return err + } + // get file checksum + pluginChecksum, err := bp.checksum(pluginDistFilePath) + if err != nil { + return err + } + bp.logger.Info("verifying plugin integrity", "checksum", pluginChecksum) + if err := bp.pm.verifier.VerifySignature(bp.pluginDefinition, pluginChecksum); err != nil { + return fmt.Errorf("unable to verify plugin signature: %v", err) + } + bp.logger.Info("unpacking plugin", "checksum", pluginChecksum) + // Unpack plugin + unPackDir, pluginMeta, err := unpackPlugin(pluginDistFilePath) + if err != nil { + return err + } + // Create Execution Command + var command *exec.Cmd + executable := path.Join(unPackDir, pluginMeta.EntryPoint) + if !common.FileExist(executable) { + return fmt.Errorf("entry point does not exist") + } + bp.logger.Debug("Plugin executable", "path", executable) + if len(pluginMeta.Parameters) == 0 { + command = exec.Command(executable) + bp.commands = []string{executable} + } else { + command = exec.Command(executable, pluginMeta.Parameters...) + bp.commands = append([]string{executable}, pluginMeta.Parameters...) + } + command.Dir = unPackDir + bp.client = plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: iplugin.DefaultHandshakeConfig, + Plugins: bp.gateways, + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Cmd: command, + AutoMTLS: true, + Logger: &logDelegate{bp.logger.New("from", "plugin")}, + }) + + bp.pluginWorkspace = unPackDir + return nil +} + +func (bp *basePlugin) Start() (err error) { + startTime := time.Now() + defer func(startTime time.Time) { + if err == nil { + bp.logger.Info("Plugin started", "took", time.Since(startTime)) + } else { + bp.logger.Error("Plugin failed to start", "error", err, "took", time.Since(startTime)) + _ = bp.Stop() + } + }(startTime) + bp.logger.Info("Starting plugin") + bp.logger.Debug("Starting plugin: Loading") + err = bp.load() + if err != nil { + return + } + bp.logger.Debug("Starting plugin: Creating client") + _, err = bp.client.Client() + if err != nil { + return + } + bp.logger.Debug("Starting plugin: Initializing") + err = bp.init() + return +} + +func (bp *basePlugin) Stop() error { + if bp.client != nil { + bp.client.Kill() + } + if bp.pluginWorkspace == "" { + return nil + } + return bp.cleanPluginWorkspace() +} + +func (bp *basePlugin) cleanPluginWorkspace() error { + workspace, err := os.Open(bp.pluginWorkspace) + if err != nil { + return err + } + defer workspace.Close() + names, err := workspace.Readdirnames(-1) + if err != nil { + return err + } + for _, name := range names { + err = os.RemoveAll(filepath.Join(bp.pluginWorkspace, name)) + if err != nil { + return err + } + } + return nil +} + +func (bp *basePlugin) init() error { + bp.logger.Info("Initializing plugin") + raw, err := bp.dispense(initializer.ConnectorName) + if err != nil { + return err + } + c, ok := raw.(initializer.PluginInitializer) + if !ok { + return fmt.Errorf("missing plugin initializer. Make sure it is in the plugin set") + } + rawConfig, err := ReadMultiFormatConfig(bp.pluginDefinition.Config) + if err != nil { + return err + } + return c.Init(context.Background(), bp.pm.nodeName, rawConfig) +} + +func (bp *basePlugin) dispense(name string) (interface{}, error) { + rpcClient, err := bp.client.Client() + if err != nil { + return nil, err + } + return rpcClient.Dispense(name) +} + +func (bp *basePlugin) Config() *PluginDefinition { + return bp.pluginDefinition +} + +func (bp *basePlugin) checksum(pluginFile string) (string, error) { + return getSha256Checksum(pluginFile) +} + +func (bp *basePlugin) Info() (PluginInterfaceName, interface{}) { + info := make(map[string]interface{}) + info["name"] = bp.pluginDefinition.Name + info["version"] = bp.pluginDefinition.Version + info["config"] = bp.pluginDefinition.Config + info["executable"] = bp.commands + return bp.pluginInterface, info +} + +type logDelegate struct { + eLogger log.Logger +} + +func (ld *logDelegate) Trace(msg string, args ...interface{}) { + ld.eLogger.Trace(msg, args...) +} + +func (ld *logDelegate) Log(level hclog.Level, msg string, args ...interface{}) { + //TODO : implement the method +} + +func (ld *logDelegate) Name() string { + return "" +} + +func (ld *logDelegate) Debug(msg string, args ...interface{}) { + ld.eLogger.Debug(msg, args...) +} + +func (ld *logDelegate) Info(msg string, args ...interface{}) { + ld.eLogger.Info(msg, args...) +} + +func (ld *logDelegate) Warn(msg string, args ...interface{}) { + ld.eLogger.Warn(msg, args...) +} + +func (ld *logDelegate) Error(msg string, args ...interface{}) { + ld.eLogger.Error(msg, args...) +} + +func (ld *logDelegate) IsTrace() bool { + return true +} + +func (*logDelegate) IsDebug() bool { + return true +} + +func (*logDelegate) IsInfo() bool { + return true +} + +func (*logDelegate) IsWarn() bool { + return true +} + +func (*logDelegate) IsError() bool { + return true +} + +func (ld *logDelegate) With(args ...interface{}) hclog.Logger { + return &logDelegate{ld.eLogger.New(args...)} +} + +func (ld *logDelegate) Named(name string) hclog.Logger { + return ld +} + +func (ld *logDelegate) ResetNamed(name string) hclog.Logger { + return ld +} + +func (ld *logDelegate) SetLevel(level hclog.Level) { +} + +func (*logDelegate) StandardLogger(opts *hclog.StandardLoggerOptions) *slog.Logger { + return nil +} + +func (*logDelegate) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + return nil +} + +// ImpliedArgs returns With key/value pairs +func (*logDelegate) ImpliedArgs() []interface{} { + return nil +} diff --git a/plugin/central.go b/plugin/central.go new file mode 100644 index 0000000000..f876679704 --- /dev/null +++ b/plugin/central.go @@ -0,0 +1,145 @@ +package plugin + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "strings" + + "github.com/ethereum/go-ethereum/log" +) + +// Central https centralClient communicating with Plugin Central +type CentralClient struct { + config *PluginCentralConfiguration + httpClient *http.Client +} + +// Create New Central Client +func NewPluginCentralClient(config *PluginCentralConfiguration) *CentralClient { + c := &CentralClient{ + config: config, + } + c.httpClient = &http.Client{} + c.httpClient.Transport = &http.Transport{ + DialTLS: c.getNewSecureDialer(), + } + return c +} + +// Builds a Dialer that supports CA Verification & Certificate Pinning. +func (cc *CentralClient) getNewSecureDialer() Dialer { + return func(network, addr string) (net.Conn, error) { + c, err := tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: cc.config.InsecureSkipTLSVerify}) + if err != nil { + return c, err + } + // support certificate pinning? + if cc.config.CertFingerprint != "" { + conState := c.ConnectionState() + for _, peercert := range conState.PeerCertificates { + if bytes.Equal(peercert.Signature[0:], []byte(cc.config.CertFingerprint)) { + return c, nil + } + } + return nil, fmt.Errorf("certificate pinning failed") + } + return c, nil + } +} + +// Get the public key from central +func (cc *CentralClient) PublicKey() ([]byte, error) { + target := fmt.Sprintf("%s/%s", cc.config.BaseURL, cc.config.PublicKeyURI) + log.Debug("downloading public key", "url", target) + readCloser, err := cc.get(target) + if err != nil { + return nil, err + } + defer func() { + _ = readCloser.Close() + }() + return ioutil.ReadAll(readCloser) +} + +// retrieve plugin signature +func (cc *CentralClient) PluginSignature(definition *PluginDefinition) ([]byte, error) { + target := fmt.Sprintf("%s/%s/%s", cc.config.BaseURL, definition.RemotePath(), definition.SignatureFileName()) + log.Debug("downloading plugin signature file", "url", target) + readCloser, err := cc.get(target) + if err != nil { + return nil, err + } + defer func() { + _ = readCloser.Close() + }() + return ioutil.ReadAll(readCloser) +} + +// retrieve plugin distribution file +func (cc *CentralClient) PluginDistribution(definition *PluginDefinition, outFilePath string) error { + target := fmt.Sprintf("%s/%s/%s", cc.config.BaseURL, definition.RemotePath(), definition.DistFileName()) + log.Debug("downloading plugin zip file", "url", target) + outFile, err := os.Create(outFilePath) + if err != nil { + return err + } + defer func() { + _ = outFile.Close() + }() + readCloser, err := cc.get(target) + if err != nil { + return err + } + defer func() { + _ = readCloser.Close() + }() + _, err = io.Copy(outFile, readCloser) + return err +} + +// perform HTTP GET +// +// caller needs to close the reader +func (cc *CentralClient) get(target string) (io.ReadCloser, error) { + if err := isValidTargetURL(cc.config.BaseURL, target); err != nil { + return nil, err + } + res, err := cc.httpClient.Get(target) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + defer func() { + _ = res.Body.Close() + }() + data, _ := ioutil.ReadAll(res.Body) + return nil, fmt.Errorf("HTTP GET error: code=%d, status=%s, body=%s", res.StatusCode, res.Status, string(data)) + } + return res.Body, nil +} + +// An adapter function for tls.Dial with CA verification & SSL Pinning support. +type Dialer func(network, addr string) (net.Conn, error) + +// Validate the target url is well formed and match base. +func isValidTargetURL(base string, target string) error { + u, err := url.Parse(target) + if err != nil { + return err + } + t, err := url.Parse(base) + if err != nil { + return err + } + if strings.Compare(t.Host, u.Host) != 0 || strings.Compare(t.Scheme, u.Scheme) != 0 { + return fmt.Errorf("target host doesnt match base host") + } + return nil +} diff --git a/plugin/central_test.go b/plugin/central_test.go new file mode 100644 index 0000000000..be5caf9400 --- /dev/null +++ b/plugin/central_test.go @@ -0,0 +1,114 @@ +package plugin + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCentralClient_PublicKey(t *testing.T) { + arbitraryServer := newTestServer("/"+DefaultPublicKeyFile, arbitraryPubKey) + defer arbitraryServer.Close() + arbitraryConfig := &PluginCentralConfiguration{ + BaseURL: arbitraryServer.URL, + PublicKeyURI: DefaultPublicKeyFile, + } + + testObject := NewPluginCentralClient(arbitraryConfig) + + actualValue, err := testObject.PublicKey() + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, arbitraryPubKey, actualValue) +} + +func TestCentralClient_PublicKey_withSSL(t *testing.T) { + arbitraryServer := httptest.NewTLSServer(newMux("/"+DefaultPublicKeyFile, arbitraryPubKey)) + defer arbitraryServer.Close() + arbitraryConfig := &PluginCentralConfiguration{ + CertFingerprint: string(arbitraryServer.Certificate().Signature), + BaseURL: arbitraryServer.URL, + PublicKeyURI: DefaultPublicKeyFile, + InsecureSkipTLSVerify: true, + } + + testObject := NewPluginCentralClient(arbitraryConfig) + + actualValue, err := testObject.PublicKey() + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, arbitraryPubKey, actualValue) +} + +func TestCentralClient_PluginSignature(t *testing.T) { + arbitraryDef := &PluginDefinition{ + Name: "arbitrary-plugin", + Version: "1.0.0", + } + arbitraryServer := newTestServer("/"+arbitraryDef.RemotePath()+"/"+arbitraryDef.SignatureFileName(), validSignature) + defer arbitraryServer.Close() + arbitraryConfig := &PluginCentralConfiguration{ + BaseURL: arbitraryServer.URL, + } + + testObject := NewPluginCentralClient(arbitraryConfig) + + actualValue, err := testObject.PluginSignature(arbitraryDef) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, validSignature, actualValue) +} + +func TestCentralClient_PluginDistribution(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + arbitraryDef := &PluginDefinition{ + Name: "arbitrary-plugin", + Version: "1.0.0", + } + arbitraryData := []byte("arbitrary data") + arbitraryServer := newTestServer("/"+arbitraryDef.RemotePath()+"/"+arbitraryDef.DistFileName(), arbitraryData) + defer arbitraryServer.Close() + arbitraryConfig := &PluginCentralConfiguration{ + BaseURL: arbitraryServer.URL, + } + + testObject := NewPluginCentralClient(arbitraryConfig) + + err = testObject.PluginDistribution(arbitraryDef, path.Join(tmpDir, "download.zip")) + + assert.NoError(t, err) +} + +func newTestServer(pattern string, returnedData []byte) *httptest.Server { + return httptest.NewServer(newMux(pattern, returnedData)) +} + +func newMux(pattern string, returnedData []byte) http.Handler { + mux := http.NewServeMux() + mux.Handle(pattern, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _, err := io.Copy(w, bytes.NewReader(returnedData)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + })) + return mux +} diff --git a/plugin/downloader.go b/plugin/downloader.go new file mode 100644 index 0000000000..a8db267c3a --- /dev/null +++ b/plugin/downloader.go @@ -0,0 +1,34 @@ +package plugin + +import ( + "fmt" + "path" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// get plugin zip file from local or remote +type Downloader struct { + pm *PluginManager +} + +func NewDownloader(pm *PluginManager) *Downloader { + return &Downloader{ + pm: pm, + } +} + +func (d *Downloader) Download(definition *PluginDefinition) (string, error) { + // check if plugin is already in the local + pluginFile := path.Join(d.pm.pluginBaseDir, definition.DistFileName()) + exist := common.FileExist(pluginFile) + log.Debug("checking plugin zip file", "path", pluginFile, "exist", exist) + if exist { + return pluginFile, nil + } + if err := d.pm.centralClient.PluginDistribution(definition, pluginFile); err != nil { + return "", fmt.Errorf("can't download from Plugin Central due to: %s. Please download the plugin manually and copy it to %s", err, d.pm.pluginBaseDir) + } + return pluginFile, nil +} diff --git a/plugin/downloader_test.go b/plugin/downloader_test.go new file mode 100644 index 0000000000..6ff55a815a --- /dev/null +++ b/plugin/downloader_test.go @@ -0,0 +1,36 @@ +package plugin + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDownloader_Download_whenPluginIsAvailableLocally(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "p-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + arbitraryPluginDistPath := path.Join(tmpDir, "arbitrary-plugin-1.0.0.zip") + if err := ioutil.WriteFile(arbitraryPluginDistPath, []byte{}, 0644); err != nil { + t.Fatal(err) + } + arbitraryPm, _ := NewPluginManager("arbitraryName", &Settings{ + BaseDir: EnvironmentAwaredValue(tmpDir), + }, false, false, "") + testObject := NewDownloader(arbitraryPm) + + actualPath, err := testObject.Download(&PluginDefinition{ + Name: "arbitrary-plugin", + Version: "1.0.0", + }) + + assert.NoError(t, err) + assert.Equal(t, arbitraryPluginDistPath, actualPath) +} diff --git a/plugin/gen/docs.markdown.tmpl b/plugin/gen/docs.markdown.tmpl new file mode 100644 index 0000000000..d9f6625683 --- /dev/null +++ b/plugin/gen/docs.markdown.tmpl @@ -0,0 +1,76 @@ + + +{{range .Files}} +{{$file_name := .Name}} + + +## {{.Name}} +{{.Description}} + +### Services + +{{range .Services}} + + +#### `{{.Name}}` +{{.Description}} + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +{{range .Methods -}} + | {{.Name}} | [{{.RequestLongType}}](#{{.RequestFullType}}){{if .RequestStreaming}} stream{{end}} | [{{.ResponseLongType}}](#{{.ResponseFullType}}){{if .ResponseStreaming}} stream{{end}} | {{nobr .Description}} | +{{end}} +{{end}} + +### Messsages + +{{range .Messages}} + + +#### `{{.LongName}}` +{{.Description}} + +{{if .HasFields}} +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +{{range .Fields -}} + | {{.Name}} | [{{.LongType}}](#{{.FullType}}) | {{.Label}} | {{nobr .Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | +{{end}} +{{end}} + +{{if .HasExtensions}} +| Extension | Type | Base | Number | Description | +| --------- | ---- | ---- | ------ | ----------- | +{{range .Extensions -}} + | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{nobr .Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}} | +{{end}} +{{end}} + +{{end}} + +{{range .Enums}} + + +#### `{{.LongName}}` +{{.Description}} + +| Name | Number | Description | +| ---- | ------ | ----------- | +{{range .Values -}} + | {{.Name}} | {{.Number}} | {{nobr .Description}} | +{{end}} + +{{end}} + +{{if .HasExtensions}} + + +#### File-level Extensions +| Extension | Type | Base | Number | Description | +| --------- | ---- | ---- | ------ | ----------- | +{{range .Extensions -}} + | {{.Name}} | {{.LongType}} | {{.ContainingLongType}} | {{.Number}} | {{nobr .Description}}{{if .DefaultValue}} Default: `{{.DefaultValue}}`{{end}} | +{{end}} +{{end}} + +{{end}} \ No newline at end of file diff --git a/plugin/gen/gen.go b/plugin/gen/gen.go new file mode 100644 index 0000000000..0a9dceadc3 --- /dev/null +++ b/plugin/gen/gen.go @@ -0,0 +1,27 @@ +// generate stub/mock files for plugins, documentation and unit tests for plugin interfaces defined in .proto files +// +// need to install: +// - protoc: 3.9.0+ +// - protoc-gen-go: 1.3.2+ +// - protoc-gen-doc: `go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc` +// - mockgen: `go get -u github.com/golang/mock/mockgen` +// - goimports: `go get -u golang.org/x/tools/cmd/goimports` +// +// go to terminal and run `go generate` from this directory + +// generate stubs +//go:generate protoc -I ../../vendor/github.com/jpmorganchase/quorum-plugin-definitions -I ../../vendor --go_out=plugins=grpc:proto_common init.proto + +// generate mocks for unit testing +//go:generate mockgen -package proto_common -destination proto_common/mock_init.go -source proto_common/init.pb.go + +// fix fmt +//go:generate goimports -w ./ + +// generate documentation +//go:generate protoc -I ../../vendor/github.com/jpmorganchase/quorum-plugin-definitions -I ../../vendor --doc_out=docs.markdown.tmpl,init_interface.md:../../docs/PluggableArchitecture/Plugins/ init.proto +//go:generate protoc -I ../../vendor/github.com/jpmorganchase/quorum-plugin-definitions -I ../../vendor --doc_out=docs.markdown.tmpl,interface.md:../../docs/PluggableArchitecture/Plugins/helloworld/ helloworld.proto +//go:generate protoc -I ../../vendor/github.com/jpmorganchase/quorum-plugin-definitions -I ../../vendor --doc_out=docs.markdown.tmpl,interface.md:../../docs/PluggableArchitecture/Plugins/security/ security.proto +//go:generate protoc -I ../../vendor/github.com/jpmorganchase/quorum-plugin-definitions -I ../../vendor --doc_out=docs.markdown.tmpl,interface.md:../../docs/PluggableArchitecture/Plugins/account/ account.proto + +package gen diff --git a/plugin/gen/proto_common/init.pb.go b/plugin/gen/proto_common/init.pb.go new file mode 100644 index 0000000000..bb7b99952a --- /dev/null +++ b/plugin/gen/proto_common/init.pb.go @@ -0,0 +1,247 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: init.proto + +package proto_common + +import ( + context "context" + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +//* +// A wrapper message to logically group other messages +type PluginInitialization struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PluginInitialization) Reset() { *m = PluginInitialization{} } +func (m *PluginInitialization) String() string { return proto.CompactTextString(m) } +func (*PluginInitialization) ProtoMessage() {} +func (*PluginInitialization) Descriptor() ([]byte, []int) { + return fileDescriptor_8d036da5b4a9bcf3, []int{0} +} + +func (m *PluginInitialization) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PluginInitialization.Unmarshal(m, b) +} +func (m *PluginInitialization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PluginInitialization.Marshal(b, m, deterministic) +} +func (m *PluginInitialization) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginInitialization.Merge(m, src) +} +func (m *PluginInitialization) XXX_Size() int { + return xxx_messageInfo_PluginInitialization.Size(m) +} +func (m *PluginInitialization) XXX_DiscardUnknown() { + xxx_messageInfo_PluginInitialization.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginInitialization proto.InternalMessageInfo + +// +// Initialization data for the plugin +type PluginInitialization_Request struct { + // `geth` node identity + HostIdentity string `protobuf:"bytes,1,opt,name=hostIdentity,proto3" json:"hostIdentity,omitempty"` + // Raw configuration to be processed by the plugin + RawConfiguration []byte `protobuf:"bytes,2,opt,name=rawConfiguration,proto3" json:"rawConfiguration,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PluginInitialization_Request) Reset() { *m = PluginInitialization_Request{} } +func (m *PluginInitialization_Request) String() string { return proto.CompactTextString(m) } +func (*PluginInitialization_Request) ProtoMessage() {} +func (*PluginInitialization_Request) Descriptor() ([]byte, []int) { + return fileDescriptor_8d036da5b4a9bcf3, []int{0, 0} +} + +func (m *PluginInitialization_Request) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PluginInitialization_Request.Unmarshal(m, b) +} +func (m *PluginInitialization_Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PluginInitialization_Request.Marshal(b, m, deterministic) +} +func (m *PluginInitialization_Request) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginInitialization_Request.Merge(m, src) +} +func (m *PluginInitialization_Request) XXX_Size() int { + return xxx_messageInfo_PluginInitialization_Request.Size(m) +} +func (m *PluginInitialization_Request) XXX_DiscardUnknown() { + xxx_messageInfo_PluginInitialization_Request.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginInitialization_Request proto.InternalMessageInfo + +func (m *PluginInitialization_Request) GetHostIdentity() string { + if m != nil { + return m.HostIdentity + } + return "" +} + +func (m *PluginInitialization_Request) GetRawConfiguration() []byte { + if m != nil { + return m.RawConfiguration + } + return nil +} + +type PluginInitialization_Response struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PluginInitialization_Response) Reset() { *m = PluginInitialization_Response{} } +func (m *PluginInitialization_Response) String() string { return proto.CompactTextString(m) } +func (*PluginInitialization_Response) ProtoMessage() {} +func (*PluginInitialization_Response) Descriptor() ([]byte, []int) { + return fileDescriptor_8d036da5b4a9bcf3, []int{0, 1} +} + +func (m *PluginInitialization_Response) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PluginInitialization_Response.Unmarshal(m, b) +} +func (m *PluginInitialization_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PluginInitialization_Response.Marshal(b, m, deterministic) +} +func (m *PluginInitialization_Response) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginInitialization_Response.Merge(m, src) +} +func (m *PluginInitialization_Response) XXX_Size() int { + return xxx_messageInfo_PluginInitialization_Response.Size(m) +} +func (m *PluginInitialization_Response) XXX_DiscardUnknown() { + xxx_messageInfo_PluginInitialization_Response.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginInitialization_Response proto.InternalMessageInfo + +func init() { + proto.RegisterType((*PluginInitialization)(nil), "proto_common.PluginInitialization") + proto.RegisterType((*PluginInitialization_Request)(nil), "proto_common.PluginInitialization.Request") + proto.RegisterType((*PluginInitialization_Response)(nil), "proto_common.PluginInitialization.Response") +} + +func init() { proto.RegisterFile("init.proto", fileDescriptor_8d036da5b4a9bcf3) } + +var fileDescriptor_8d036da5b4a9bcf3 = []byte{ + // 210 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xca, 0xcc, 0xcb, 0x2c, + 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x53, 0xf1, 0xc9, 0xf9, 0xb9, 0xb9, 0xf9, + 0x79, 0x4a, 0xb5, 0x5c, 0x22, 0x01, 0x39, 0xa5, 0xe9, 0x99, 0x79, 0x9e, 0x79, 0x99, 0x25, 0x99, + 0x89, 0x39, 0x99, 0x55, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x52, 0x91, 0x5c, 0xec, 0x41, 0xa9, 0x85, + 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x4a, 0x5c, 0x3c, 0x19, 0xf9, 0xc5, 0x25, 0x9e, 0x29, 0xa9, 0x79, + 0x25, 0x99, 0x25, 0x95, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x28, 0x62, 0x42, 0x5a, 0x5c, + 0x02, 0x45, 0x89, 0xe5, 0xce, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0xa5, 0x45, 0x60, 0x23, 0x24, 0x98, + 0x14, 0x18, 0x35, 0x78, 0x82, 0x30, 0xc4, 0xa5, 0xb8, 0xb8, 0x38, 0x82, 0x52, 0x8b, 0x0b, 0xf2, + 0xf3, 0x8a, 0x53, 0x8d, 0x4a, 0xb8, 0x04, 0xd1, 0xac, 0x4f, 0x2d, 0x12, 0x8a, 0xe7, 0x62, 0x01, + 0x71, 0x85, 0xb4, 0xf4, 0x90, 0x9d, 0xaa, 0x87, 0xcd, 0x9d, 0x7a, 0x50, 0x47, 0x4a, 0x69, 0x13, + 0xa5, 0x16, 0x62, 0xab, 0x93, 0x09, 0x97, 0x78, 0x72, 0x7e, 0xae, 0x5e, 0x61, 0x69, 0x7e, 0x51, + 0x69, 0xae, 0x5e, 0x01, 0x58, 0x2d, 0x44, 0xbf, 0x13, 0x37, 0x92, 0x43, 0xa2, 0x50, 0x82, 0x2a, + 0x89, 0x0d, 0xcc, 0x33, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x97, 0x47, 0x58, 0xf3, 0x4d, 0x01, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// PluginInitializerClient is the client API for PluginInitializer service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type PluginInitializerClient interface { + Init(ctx context.Context, in *PluginInitialization_Request, opts ...grpc.CallOption) (*PluginInitialization_Response, error) +} + +type pluginInitializerClient struct { + cc *grpc.ClientConn +} + +func NewPluginInitializerClient(cc *grpc.ClientConn) PluginInitializerClient { + return &pluginInitializerClient{cc} +} + +func (c *pluginInitializerClient) Init(ctx context.Context, in *PluginInitialization_Request, opts ...grpc.CallOption) (*PluginInitialization_Response, error) { + out := new(PluginInitialization_Response) + err := c.cc.Invoke(ctx, "/proto_common.PluginInitializer/Init", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PluginInitializerServer is the server API for PluginInitializer service. +type PluginInitializerServer interface { + Init(context.Context, *PluginInitialization_Request) (*PluginInitialization_Response, error) +} + +// UnimplementedPluginInitializerServer can be embedded to have forward compatible implementations. +type UnimplementedPluginInitializerServer struct { +} + +func (*UnimplementedPluginInitializerServer) Init(ctx context.Context, req *PluginInitialization_Request) (*PluginInitialization_Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Init not implemented") +} + +func RegisterPluginInitializerServer(s *grpc.Server, srv PluginInitializerServer) { + s.RegisterService(&_PluginInitializer_serviceDesc, srv) +} + +func _PluginInitializer_Init_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PluginInitialization_Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PluginInitializerServer).Init(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto_common.PluginInitializer/Init", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PluginInitializerServer).Init(ctx, req.(*PluginInitialization_Request)) + } + return interceptor(ctx, in, info, handler) +} + +var _PluginInitializer_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto_common.PluginInitializer", + HandlerType: (*PluginInitializerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Init", + Handler: _PluginInitializer_Init_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "init.proto", +} diff --git a/plugin/gen/proto_common/mock_init.go b/plugin/gen/proto_common/mock_init.go new file mode 100644 index 0000000000..0a2e314fd7 --- /dev/null +++ b/plugin/gen/proto_common/mock_init.go @@ -0,0 +1,94 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: proto_common/init.pb.go + +// Package proto_common is a generated GoMock package. +package proto_common + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + grpc "google.golang.org/grpc" +) + +// MockPluginInitializerClient is a mock of PluginInitializerClient interface +type MockPluginInitializerClient struct { + ctrl *gomock.Controller + recorder *MockPluginInitializerClientMockRecorder +} + +// MockPluginInitializerClientMockRecorder is the mock recorder for MockPluginInitializerClient +type MockPluginInitializerClientMockRecorder struct { + mock *MockPluginInitializerClient +} + +// NewMockPluginInitializerClient creates a new mock instance +func NewMockPluginInitializerClient(ctrl *gomock.Controller) *MockPluginInitializerClient { + mock := &MockPluginInitializerClient{ctrl: ctrl} + mock.recorder = &MockPluginInitializerClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockPluginInitializerClient) EXPECT() *MockPluginInitializerClientMockRecorder { + return m.recorder +} + +// Init mocks base method +func (m *MockPluginInitializerClient) Init(ctx context.Context, in *PluginInitialization_Request, opts ...grpc.CallOption) (*PluginInitialization_Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Init", varargs...) + ret0, _ := ret[0].(*PluginInitialization_Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Init indicates an expected call of Init +func (mr *MockPluginInitializerClientMockRecorder) Init(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockPluginInitializerClient)(nil).Init), varargs...) +} + +// MockPluginInitializerServer is a mock of PluginInitializerServer interface +type MockPluginInitializerServer struct { + ctrl *gomock.Controller + recorder *MockPluginInitializerServerMockRecorder +} + +// MockPluginInitializerServerMockRecorder is the mock recorder for MockPluginInitializerServer +type MockPluginInitializerServerMockRecorder struct { + mock *MockPluginInitializerServer +} + +// NewMockPluginInitializerServer creates a new mock instance +func NewMockPluginInitializerServer(ctrl *gomock.Controller) *MockPluginInitializerServer { + mock := &MockPluginInitializerServer{ctrl: ctrl} + mock.recorder = &MockPluginInitializerServerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockPluginInitializerServer) EXPECT() *MockPluginInitializerServerMockRecorder { + return m.recorder +} + +// Init mocks base method +func (m *MockPluginInitializerServer) Init(arg0 context.Context, arg1 *PluginInitialization_Request) (*PluginInitialization_Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Init", arg0, arg1) + ret0, _ := ret[0].(*PluginInitialization_Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Init indicates an expected call of Init +func (mr *MockPluginInitializerServerMockRecorder) Init(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockPluginInitializerServer)(nil).Init), arg0, arg1) +} diff --git a/plugin/helloworld/connector.go b/plugin/helloworld/connector.go new file mode 100644 index 0000000000..d83b611f65 --- /dev/null +++ b/plugin/helloworld/connector.go @@ -0,0 +1,26 @@ +package helloworld + +import ( + "context" + + iplugin "github.com/ethereum/go-ethereum/internal/plugin" + "github.com/hashicorp/go-plugin" + "github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go/proto" + "google.golang.org/grpc" +) + +const ConnectorName = "ping" + +type PluginConnector struct { + plugin.Plugin +} + +func (p *PluginConnector) GRPCServer(b *plugin.GRPCBroker, s *grpc.Server) error { + return iplugin.ErrNotSupported +} + +func (p *PluginConnector) GRPCClient(ctx context.Context, b *plugin.GRPCBroker, cc *grpc.ClientConn) (interface{}, error) { + return &PluginGateway{ + client: proto.NewPluginGreetingClient(cc), + }, nil +} diff --git a/plugin/helloworld/gateway.go b/plugin/helloworld/gateway.go new file mode 100644 index 0000000000..cee4bec343 --- /dev/null +++ b/plugin/helloworld/gateway.go @@ -0,0 +1,21 @@ +package helloworld + +import ( + "context" + + "github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go/proto" +) + +type PluginGateway struct { + client proto.PluginGreetingClient +} + +func (p *PluginGateway) Greeting(ctx context.Context, msg string) (string, error) { + resp, err := p.client.Greeting(ctx, &proto.PluginHelloWorld_Request{ + Msg: msg, + }) + if err != nil { + return "", err + } + return resp.Msg, nil +} diff --git a/plugin/helloworld/gateway_test.go b/plugin/helloworld/gateway_test.go new file mode 100644 index 0000000000..6ea47b8c8a --- /dev/null +++ b/plugin/helloworld/gateway_test.go @@ -0,0 +1,32 @@ +package helloworld + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go/mock_proto" + "github.com/jpmorganchase/quorum-hello-world-plugin-sdk-go/proto" + "github.com/stretchr/testify/assert" +) + +func TestPluginPingPong_Ping(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + req := &proto.PluginHelloWorld_Request{ + Msg: "arbitrary msg", + } + mockClient := mock_proto.NewMockPluginGreetingClient(ctrl) + mockClient. + EXPECT(). + Greeting(gomock.Any(), gomock.Eq(req)). + Return(&proto.PluginHelloWorld_Response{ + Msg: "arbitrary response", + }, nil) + testObject := &PluginGateway{client: mockClient} + + resp, err := testObject.Greeting(context.Background(), "arbitrary msg") + + assert.NoError(t, err) + assert.Equal(t, "arbitrary response", resp) +} diff --git a/plugin/helloworld/service.go b/plugin/helloworld/service.go new file mode 100644 index 0000000000..1aa654284f --- /dev/null +++ b/plugin/helloworld/service.go @@ -0,0 +1,21 @@ +package helloworld + +import "context" + +type PluginHelloWorld interface { + Greeting(ctx context.Context, msg string) (string, error) +} + +type PluginHelloWorldDeferFunc func() (PluginHelloWorld, error) + +type ReloadablePluginHelloWorld struct { + DeferFunc PluginHelloWorldDeferFunc +} + +func (d *ReloadablePluginHelloWorld) Greeting(ctx context.Context, msg string) (string, error) { + p, err := d.DeferFunc() + if err != nil { + return "", err + } + return p.Greeting(ctx, msg) +} diff --git a/plugin/initializer/connector.go b/plugin/initializer/connector.go new file mode 100644 index 0000000000..1ee9260ce6 --- /dev/null +++ b/plugin/initializer/connector.go @@ -0,0 +1,26 @@ +package initializer + +import ( + "context" + + iplugin "github.com/ethereum/go-ethereum/internal/plugin" + "github.com/ethereum/go-ethereum/plugin/gen/proto_common" + "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +const ConnectorName = "init" + +type PluginConnector struct { + plugin.Plugin +} + +func (p *PluginConnector) GRPCServer(b *plugin.GRPCBroker, s *grpc.Server) error { + return iplugin.ErrNotSupported +} + +func (p *PluginConnector) GRPCClient(ctx context.Context, b *plugin.GRPCBroker, cc *grpc.ClientConn) (interface{}, error) { + return &PluginGateway{ + client: proto_common.NewPluginInitializerClient(cc), + }, nil +} diff --git a/plugin/initializer/gateway.go b/plugin/initializer/gateway.go new file mode 100644 index 0000000000..ddd42bcbb0 --- /dev/null +++ b/plugin/initializer/gateway.go @@ -0,0 +1,19 @@ +package initializer + +import ( + "context" + + "github.com/ethereum/go-ethereum/plugin/gen/proto_common" +) + +type PluginGateway struct { + client proto_common.PluginInitializerClient +} + +func (g *PluginGateway) Init(ctx context.Context, nodeIdentity string, rawConfiguration []byte) error { + _, err := g.client.Init(ctx, &proto_common.PluginInitialization_Request{ + HostIdentity: nodeIdentity, + RawConfiguration: rawConfiguration, + }) + return err +} diff --git a/plugin/initializer/gateway_test.go b/plugin/initializer/gateway_test.go new file mode 100644 index 0000000000..1287d3bafa --- /dev/null +++ b/plugin/initializer/gateway_test.go @@ -0,0 +1,32 @@ +package initializer + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/plugin/gen/proto_common" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestPluginGateway_Init(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + req := &proto_common.PluginInitialization_Request{ + HostIdentity: "arbitraryName", + RawConfiguration: []byte("arbitrary config"), + } + + mockClient := proto_common.NewMockPluginInitializerClient(ctrl) + mockClient. + EXPECT(). + Init(gomock.Any(), gomock.Eq(req)). + Return(&proto_common.PluginInitialization_Response{}, nil) + + testObject := &PluginGateway{client: mockClient} + + err := testObject.Init(context.Background(), req.HostIdentity, req.RawConfiguration) + + assert.NoError(t, err) +} diff --git a/plugin/initializer/service.go b/plugin/initializer/service.go new file mode 100644 index 0000000000..25d7f12cda --- /dev/null +++ b/plugin/initializer/service.go @@ -0,0 +1,7 @@ +package initializer + +import "context" + +type PluginInitializer interface { + Init(ctx context.Context, nodeIdentity string, rawConfiguration []byte) error +} diff --git a/plugin/local_verifier.go b/plugin/local_verifier.go new file mode 100644 index 0000000000..70f19bc17f --- /dev/null +++ b/plugin/local_verifier.go @@ -0,0 +1,52 @@ +package plugin + +import ( + "fmt" + "io/ioutil" + "os" + "path" +) + +const DefaultPublicKeyFile = "Central.pgp.pk" + +// Local Implementation of plugin.Verifier +type LocalVerifier struct { + PublicKeyPath string // where to obtain PGP public key + SignatureBaseDir string // where to obtain plugin signature file +} + +// Build a new LocalVerifier +func NewLocalVerifier(publicKeyPath string, pluginSignatureBaseDir string) (*LocalVerifier, error) { + if _, err := os.Stat(publicKeyPath); os.IsNotExist(err) { + return nil, err + } + stat, err := os.Stat(pluginSignatureBaseDir) + if os.IsNotExist(err) { + return nil, err + } + if !stat.Mode().IsDir() { + return nil, fmt.Errorf("pluginSignatureBaseDir is not a directory") + } + verifier := &LocalVerifier{ + PublicKeyPath: publicKeyPath, + SignatureBaseDir: pluginSignatureBaseDir, + } + return verifier, nil +} + +// Verify a plugin giving its name from Central +func (v *LocalVerifier) VerifySignature(definition *PluginDefinition, checksum string) error { + pluginSigPath := path.Join(v.SignatureBaseDir, definition.SignatureFileName()) + if _, err := os.Stat(pluginSigPath); os.IsNotExist(err) { + return err + } + pubkey, err := ioutil.ReadFile(v.PublicKeyPath) + if err != nil { + return err + } + sig, err := ioutil.ReadFile(pluginSigPath) + if err != nil { + return err + } + return verify(sig, pubkey, checksum) +} diff --git a/plugin/local_verifier_test.go b/plugin/local_verifier_test.go new file mode 100644 index 0000000000..bb677829fa --- /dev/null +++ b/plugin/local_verifier_test.go @@ -0,0 +1,39 @@ +package plugin + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocalVerifier_VerifySignature(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + arbitraryPluginDefinition := &PluginDefinition{ + Name: "arbitrary-plugin", + Version: "1.0.0", + Config: nil, + } + pubKeyFile := path.Join(tmpDir, "pubkey") + if err := ioutil.WriteFile(pubKeyFile, signerPubKey, 0644); err != nil { + t.Fatal(err) + } + sigFile := path.Join(tmpDir, arbitraryPluginDefinition.SignatureFileName()) + if err := ioutil.WriteFile(sigFile, validSignature, 0644); err != nil { + t.Fatal(err) + } + + testObject, err := NewLocalVerifier(pubKeyFile, tmpDir) + if err != nil { + t.Fatal(err) + } + assert.NoError(t, testObject.VerifySignature(arbitraryPluginDefinition, arbitraryChecksum)) +} diff --git a/plugin/online_verifier.go b/plugin/online_verifier.go new file mode 100644 index 0000000000..ee0a18bdcc --- /dev/null +++ b/plugin/online_verifier.go @@ -0,0 +1,23 @@ +package plugin + +// Implementation of plugin.Verifier that uses remote server to verify plugins. +type OnlineVerifier struct { + centralClient *CentralClient +} + +func NewOnlineVerifier(centralClient *CentralClient) *OnlineVerifier { + return &OnlineVerifier{centralClient: centralClient} +} + +// Verify a plugin giving its name from Central +func (v *OnlineVerifier) VerifySignature(definition *PluginDefinition, checksum string) error { + sig, err := v.centralClient.PluginSignature(definition) + if err != nil { + return err + } + pubkey, err := v.centralClient.PublicKey() + if err != nil { + return err + } + return verify(sig, pubkey, checksum) +} diff --git a/plugin/plugin_templates.go b/plugin/plugin_templates.go new file mode 100644 index 0000000000..ab04785845 --- /dev/null +++ b/plugin/plugin_templates.go @@ -0,0 +1,98 @@ +package plugin + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/plugin/account" + "github.com/ethereum/go-ethereum/plugin/helloworld" + "github.com/ethereum/go-ethereum/plugin/security" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// a template that returns the hello world plugin instance +type HelloWorldPluginTemplate struct { + *basePlugin +} + +func (p *HelloWorldPluginTemplate) Get() (helloworld.PluginHelloWorld, error) { + return &helloworld.ReloadablePluginHelloWorld{ + DeferFunc: func() (helloworld.PluginHelloWorld, error) { + raw, err := p.dispense(helloworld.ConnectorName) + if err != nil { + return nil, err + } + return raw.(helloworld.PluginHelloWorld), nil + }, + }, nil +} + +type SecurityPluginTemplate struct { + *basePlugin +} + +// TLSConfigurationSource returns an implementation of security.TLSConfigurationSource which could be nil +// in case the plugin doesn't implement the corresponding service. In order to verify that, it attempts +// to make a call and inspect the error. +func (sp *SecurityPluginTemplate) TLSConfigurationSource() (security.TLSConfigurationSource, error) { + raw, err := sp.dispense(security.TLSConfigurationConnectorName) + if err != nil { + return nil, err + } + tlsConfigurationSource := raw.(security.TLSConfigurationSource) + // try to invoke the method to test if the plugin actually implements the service + _, err = tlsConfigurationSource.Get(context.Background()) + rpcStatus, ok := status.FromError(err) + if ok && rpcStatus.Code() == codes.Unimplemented { + log.Info("Security: Plugin doesn't implement TLSConfigurationSource service", "err", err) + return nil, nil + } + return tlsConfigurationSource, nil +} + +// AuthenticationManager returns an implementation of security.AuthenticationManager which could be +// a deferred implemenation or a disabled implementation. +// +// The deferred implementation delegates to the actual implemenation (which is the plugin client). +// +// The disabled implementation allows no authentication verification. +func (sp *SecurityPluginTemplate) AuthenticationManager() (security.AuthenticationManager, error) { + deferFunc := func() (security.AuthenticationManager, error) { + raw, err := sp.dispense(security.AuthenticationConnectorName) + if err != nil { + return nil, err + } + return raw.(security.AuthenticationManager), nil + } + if am, err := deferFunc(); err != nil { + return nil, err + } else { + // try to invoke the method to test if the plugin actually implements the service + _, err = am.Authenticate(context.Background(), "") + rpcStatus, ok := status.FromError(err) + if ok && rpcStatus.Code() == codes.Unimplemented { + log.Info("Security: Plugin doesn't implement AuthenticationManager service", "err", err) + return security.NewDisabledAuthenticationManager(), nil + } + } + return security.NewDeferredAuthenticationManager(deferFunc), nil +} + +type ReloadableAccountServiceFactory struct { + *basePlugin +} + +func (f *ReloadableAccountServiceFactory) Create() (account.Service, error) { + am := &account.ReloadableService{ + DispenseFunc: func() (account.Service, error) { + raw, err := f.dispense(account.ConnectorName) + if err != nil { + return nil, err + } + return raw.(account.Service), nil + }, + } + + return am, nil +} diff --git a/plugin/security/connector.go b/plugin/security/connector.go new file mode 100644 index 0000000000..aee2bc81d4 --- /dev/null +++ b/plugin/security/connector.go @@ -0,0 +1,43 @@ +package security + +import ( + "context" + + iplugin "github.com/ethereum/go-ethereum/internal/plugin" + "github.com/hashicorp/go-plugin" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + "google.golang.org/grpc" +) + +const ( + TLSConfigurationConnectorName = "tls" + AuthenticationConnectorName = "auth" +) + +type TLSConfigurationSourcePluginConnector struct { + plugin.Plugin +} + +func (*TLSConfigurationSourcePluginConnector) GRPCServer(b *plugin.GRPCBroker, s *grpc.Server) error { + return iplugin.ErrNotSupported +} + +func (*TLSConfigurationSourcePluginConnector) GRPCClient(ctx context.Context, b *plugin.GRPCBroker, cc *grpc.ClientConn) (interface{}, error) { + return &TLSConfigurationSourcePluginGateway{ + client: proto.NewTLSConfigurationSourceClient(cc), + }, nil +} + +type AuthenticationManagerPluginConnector struct { + plugin.Plugin +} + +func (*AuthenticationManagerPluginConnector) GRPCServer(b *plugin.GRPCBroker, s *grpc.Server) error { + return iplugin.ErrNotSupported +} + +func (*AuthenticationManagerPluginConnector) GRPCClient(ctx context.Context, b *plugin.GRPCBroker, cc *grpc.ClientConn) (interface{}, error) { + return &AuthenticationManagerPluginGateway{ + client: proto.NewAuthenticationManagerClient(cc), + }, nil +} diff --git a/plugin/security/gateway.go b/plugin/security/gateway.go new file mode 100644 index 0000000000..1dbd372bd3 --- /dev/null +++ b/plugin/security/gateway.go @@ -0,0 +1,88 @@ +package security + +import ( + "context" + "crypto/tls" + "errors" + "math" + + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" +) + +var ( + // harden the cipher strength by only using ciphers >=256bits + defaultCipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + } +) + +type TLSConfigurationSourcePluginGateway struct { + client proto.TLSConfigurationSourceClient +} + +func (c *TLSConfigurationSourcePluginGateway) Get(ctx context.Context) (*tls.Config, error) { + resp, err := c.client.Get(ctx, &proto.TLSConfiguration_Request{}) + if err != nil { + return nil, err + } + if resp == nil || resp.GetData() == nil { // no tls config + return nil, nil + } + return transform(resp.GetData()) +} + +// transform raw configuration received from the plugin to `tls.Config` object being used +// to configure TLS for JSON RPC servers +// The customized tls.Config follows: https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go +func transform(tlsData *proto.TLSConfiguration_Data) (*tls.Config, error) { + tlsConfig := &tls.Config{ + // prioritize curve preferences from crypto/tls/common.go#defaultCurvePreferences + CurvePreferences: []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + tls.X25519, + }, + // Support only TLS1.2 & Above + MinVersion: tls.VersionTLS12, + } + receivedCipherSuites := tlsData.GetCipherSuites() + cipherSuites := make([]uint16, len(receivedCipherSuites)) + if len(receivedCipherSuites) > 0 { + for i, cs := range receivedCipherSuites { + if cs > math.MaxUint16 { + return nil, errors.New("cipher suite value overflow") + } + cipherSuites[i] = uint16(cs) + } + } else { + cipherSuites = defaultCipherSuites + } + tlsConfig.CipherSuites = cipherSuites + tlsConfig.PreferServerCipherSuites = true + + cer, err := tls.X509KeyPair(tlsData.GetCertPem(), tlsData.GetKeyPem()) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{cer} + + return tlsConfig, nil +} + +type AuthenticationManagerPluginGateway struct { + client proto.AuthenticationManagerClient +} + +func (a *AuthenticationManagerPluginGateway) Authenticate(ctx context.Context, token string) (*proto.PreAuthenticatedAuthenticationToken, error) { + return a.client.Authenticate(ctx, &proto.AuthenticationToken{ + RawToken: []byte(token), + }) +} + +func (a *AuthenticationManagerPluginGateway) IsEnabled(ctx context.Context) (bool, error) { + return true, nil +} diff --git a/plugin/security/gateway_test.go b/plugin/security/gateway_test.go new file mode 100644 index 0000000000..4523a10d98 --- /dev/null +++ b/plugin/security/gateway_test.go @@ -0,0 +1,157 @@ +package security + +import ( + "context" + "crypto/tls" + "math" + "testing" + + "github.com/golang/mock/gomock" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/mock_proto" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + testifyassert "github.com/stretchr/testify/assert" +) + +const ( + rsaCertPem = `-----BEGIN CERTIFICATE----- +MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ +hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa +rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv +zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW +r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V +-----END CERTIFICATE----- +` + rsaKeyPem = `-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo +k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G +6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N +MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW +SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T +xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi +D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g== +-----END RSA PRIVATE KEY----- +` +) + +var ( + abitraryTLSConfigurationData = &proto.TLSConfiguration_Data{ + CertPem: []byte(rsaCertPem), + KeyPem: []byte(rsaKeyPem), + } +) + +func TestTransform_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + cfg, err := transform(abitraryTLSConfigurationData) + + assert.NoError(err) + assert.True(cfg.PreferServerCipherSuites) + assert.EqualValues(defaultCipherSuites, cfg.CipherSuites) + assert.Equal(uint16(tls.VersionTLS12), cfg.MinVersion) + assert.EqualValues([]tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + tls.X25519, + }, cfg.CurvePreferences) +} + +func TestTransform_whenUsingCustomCipherSuites(t *testing.T) { + defer func() { + abitraryTLSConfigurationData.CipherSuites = nil + }() + assert := testifyassert.New(t) + + abitraryTLSConfigurationData.CipherSuites = []uint32{uint32(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)} + + cfg, err := transform(abitraryTLSConfigurationData) + + assert.NoError(err) + assert.Contains(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) +} + +func TestTransform_whenCipherSuiteOverflow(t *testing.T) { + defer func() { + abitraryTLSConfigurationData.CipherSuites = nil + }() + assert := testifyassert.New(t) + + abitraryTLSConfigurationData.CipherSuites = []uint32{math.MaxInt32} + + _, err := transform(abitraryTLSConfigurationData) + + assert.Error(err) +} + +func TestTLSConfigurationSourcePluginGateway_Get(t *testing.T) { + assert := testifyassert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_proto.NewMockTLSConfigurationSourceClient(ctrl) + mockClient. + EXPECT(). + Get(gomock.Any(), gomock.Any()). + Return(&proto.TLSConfiguration_Response{ + Data: abitraryTLSConfigurationData, + }, nil) + + testObject := &TLSConfigurationSourcePluginGateway{client: mockClient} + + tlsConfig, err := testObject.Get(context.Background()) + + assert.NoError(err) + assert.NotNil(tlsConfig) +} + +func TestTLSConfigurationSourcePluginGateway_Get_whenNoConfigurationData(t *testing.T) { + assert := testifyassert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_proto.NewMockTLSConfigurationSourceClient(ctrl) + mockClient. + EXPECT(). + Get(gomock.Any(), gomock.Any()). + Return(&proto.TLSConfiguration_Response{}, nil) + + testObject := &TLSConfigurationSourcePluginGateway{client: mockClient} + + tlsConfig, err := testObject.Get(context.Background()) + + assert.NoError(err) + assert.Nil(tlsConfig) +} + +func TestAuthenticationManagerPluginGateway_IsEnabled_always(t *testing.T) { + testObject := &AuthenticationManagerPluginGateway{} + + ret, err := testObject.IsEnabled(context.Background()) + + testifyassert.NoError(t, err) + testifyassert.True(t, ret) +} + +func TestAuthenticationManagerPluginGateway_Authenticate(t *testing.T) { + assert := testifyassert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + arbitraryPreauthenticatedToken := &proto.AuthenticationToken{ + RawToken: []byte("arbitrary token"), + } + mockClient := mock_proto.NewMockAuthenticationManagerClient(ctrl) + mockClient. + EXPECT(). + Authenticate(gomock.Any(), gomock.Eq(arbitraryPreauthenticatedToken)). + Return(&proto.PreAuthenticatedAuthenticationToken{}, nil) + + testObject := &AuthenticationManagerPluginGateway{client: mockClient} + + _, err := testObject.Authenticate(context.Background(), string(arbitraryPreauthenticatedToken.RawToken)) + + assert.NoError(err) +} diff --git a/plugin/security/service.go b/plugin/security/service.go new file mode 100644 index 0000000000..30cce8b06d --- /dev/null +++ b/plugin/security/service.go @@ -0,0 +1,61 @@ +package security + +import ( + "context" + "crypto/tls" + "errors" + + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" +) + +type TLSConfigurationSource interface { + Get(ctx context.Context) (*tls.Config, error) +} + +type AuthenticationManager interface { + Authenticate(ctx context.Context, token string) (*proto.PreAuthenticatedAuthenticationToken, error) + IsEnabled(ctx context.Context) (bool, error) +} + +type AuthenticationManagerDeferFunc func() (AuthenticationManager, error) + +type DeferredAuthenticationManager struct { + deferFunc AuthenticationManagerDeferFunc +} + +func (d *DeferredAuthenticationManager) Authenticate(ctx context.Context, token string) (*proto.PreAuthenticatedAuthenticationToken, error) { + am, err := d.deferFunc() + if err != nil { + return nil, err + } + return am.Authenticate(ctx, token) +} + +func (d *DeferredAuthenticationManager) IsEnabled(ctx context.Context) (bool, error) { + am, err := d.deferFunc() + if err != nil { + return false, err + } + return am.IsEnabled(ctx) +} + +func NewDeferredAuthenticationManager(deferFunc AuthenticationManagerDeferFunc) *DeferredAuthenticationManager { + return &DeferredAuthenticationManager{ + deferFunc: deferFunc, + } +} + +type DisabledAuthenticationManager struct { +} + +func (*DisabledAuthenticationManager) Authenticate(ctx context.Context, token string) (*proto.PreAuthenticatedAuthenticationToken, error) { + return nil, errors.New("not supported operation") +} + +func (*DisabledAuthenticationManager) IsEnabled(ctx context.Context) (bool, error) { + return false, nil +} + +func NewDisabledAuthenticationManager() AuthenticationManager { + return &DisabledAuthenticationManager{} +} diff --git a/plugin/security/utils.go b/plugin/security/utils.go new file mode 100644 index 0000000000..f820d11163 --- /dev/null +++ b/plugin/security/utils.go @@ -0,0 +1,57 @@ +package security + +import ( + "crypto/tls" + "fmt" +) + +type CipherSuite string +type CipherSuiteList []CipherSuite + +var ( + // copy from crypto/tls/cipher_suites.go per go 1.13.8 + supportedCipherSuites = map[CipherSuite]uint16{ + "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, + "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, + "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, + "TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + "TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + } +) + +func (cs CipherSuite) toUint16() (uint16, error) { + v, ok := supportedCipherSuites[cs] + if ok { + return v, nil + } + return 0, fmt.Errorf("not supported cipher suite %s", cs) +} + +func (csl CipherSuiteList) ToUint16Array() ([]uint16, error) { + a := make([]uint16, len(csl)) + for i, cs := range csl { + v, err := cs.toUint16() + if err != nil { + return nil, err + } + a[i] = v + } + return a, nil +} diff --git a/plugin/service.go b/plugin/service.go new file mode 100644 index 0000000000..22023b4998 --- /dev/null +++ b/plugin/service.go @@ -0,0 +1,238 @@ +package plugin + +import ( + "fmt" + "reflect" + "sync" + "unsafe" + + "github.com/ethereum/go-ethereum/accounts/pluggable" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +// this implements geth service +type PluginManager struct { + nodeName string // geth node name + pluginBaseDir string // base directory for all the plugins + verifier Verifier + centralClient *CentralClient + downloader *Downloader + settings *Settings + mux sync.Mutex // control concurrent access to plugins cache + plugins map[PluginInterfaceName]managedPlugin // lazy load the actual plugin templates + initializedPlugins map[PluginInterfaceName]managedPlugin // prepopulate during initialization of plugin manager, needed for starting/stopping/getting info +} + +func (s *PluginManager) Protocols() []p2p.Protocol { return nil } + +// this is called after PluginManager service has been successfully started +// See node/node.go#Start() +func (s *PluginManager) APIs() []rpc.API { + return append([]rpc.API{ + { + Namespace: "admin", + Service: NewPluginManagerAPI(s), + Version: "1.0", + Public: false, + }, + }, s.delegateAPIs()...) +} + +func (s *PluginManager) Start(_ *p2p.Server) (err error) { + log.Info("Starting all plugins", "count", len(s.initializedPlugins)) + startedPlugins := make([]managedPlugin, 0, len(s.initializedPlugins)) + for _, p := range s.initializedPlugins { + if err = p.Start(); err != nil { + break + } else { + startedPlugins = append(startedPlugins, p) + } + } + if err != nil { + for _, p := range startedPlugins { + _ = p.Stop() + } + } + return +} + +func (s *PluginManager) getPlugin(name PluginInterfaceName) (managedPlugin, bool) { + s.mux.Lock() + defer s.mux.Unlock() + p, ok := s.plugins[name] // check if it's been used before + if !ok { + p, ok = s.initializedPlugins[name] // check if it's been initialized before + } + return p, ok +} + +// Check if a plugin is enabled/setup +func (s *PluginManager) IsEnabled(name PluginInterfaceName) bool { + _, ok := s.initializedPlugins[name] + return ok +} + +// store the plugin instance to the value of the pointer v and cache it +// this function makes sure v value will never be nil +func (s *PluginManager) GetPluginTemplate(name PluginInterfaceName, v managedPlugin) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return fmt.Errorf("invalid argument value, expected a pointer but got %s", reflect.TypeOf(v)) + } + recoverToErrorFunc := func(f func()) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%s", r) + } + }() + f() + return + } + if p, ok := s.plugins[name]; ok { + return recoverToErrorFunc(func() { + cachedValue := reflect.ValueOf(p) + rv.Elem().Set(cachedValue.Elem()) + }) + } + base, ok := s.initializedPlugins[name] + if !ok { + return fmt.Errorf("plugin: [%s] is not found", name) + } + if err := recoverToErrorFunc(func() { + basePluginValue := reflect.ValueOf(base) + // the first field in the plugin template object is the basePlugin + // it indicates that the plugin template "extends" basePlugin + basePluginField := rv.Elem().FieldByName("basePlugin") + if !basePluginField.IsValid() || basePluginField.Type() != basePluginPointerType { + panic("plugin template must extend *basePlugin") + } + // need to have write access to the unexported field in the target object + basePluginField = reflect.NewAt(basePluginField.Type(), unsafe.Pointer(basePluginField.UnsafeAddr())).Elem() + basePluginField.Set(basePluginValue) + }); err != nil { + return err + } + s.mux.Lock() + defer s.mux.Unlock() + s.plugins[name] = v + return nil +} + +func (s *PluginManager) Stop() error { + log.Info("Stopping all plugins", "count", len(s.initializedPlugins)) + allErrors := make([]error, 0) + for _, p := range s.initializedPlugins { + if err := p.Stop(); err != nil { + allErrors = append(allErrors, err) + } + } + log.Info("All plugins stopped", "errors", allErrors) + if len(allErrors) == 0 { + return nil + } + return fmt.Errorf("%s", allErrors) +} + +// Provide details of current plugins being used +func (s *PluginManager) PluginsInfo() interface{} { + info := make(map[PluginInterfaceName]interface{}) + if len(s.initializedPlugins) == 0 { + return info + } + info["baseDir"] = s.pluginBaseDir + for _, p := range s.initializedPlugins { + k, v := p.Info() + info[k] = v + } + return info +} + +// AddAccountPluginToBackend adds the account plugin to the provided account backend +func (s *PluginManager) AddAccountPluginToBackend(b *pluggable.Backend) error { + v := new(ReloadableAccountServiceFactory) + if err := s.GetPluginTemplate(AccountPluginInterfaceName, v); err != nil { + return err + } + service, err := v.Create() + if err != nil { + return err + } + if err := b.SetPluginService(service); err != nil { + return err + } + return nil +} + +func (s *PluginManager) Reload(name PluginInterfaceName) (bool, error) { + p, ok := s.getPlugin(name) + if !ok { + return false, fmt.Errorf("no such plugin provider: %s", name) + } + _ = p.Stop() + if err := p.Start(); err != nil { + return false, err + } + return true, nil +} + +// this is to configure delegate APIs call to the plugins +func (s *PluginManager) delegateAPIs() []rpc.API { + apis := make([]rpc.API, 0) + for _, p := range s.initializedPlugins { + interfaceName, _ := p.Info() + if pluginProvider, ok := pluginProviders[interfaceName]; ok { + if pluginProvider.apiProviderFunc != nil { + namespace := fmt.Sprintf("plugin@%s", interfaceName) + log.Debug("adding RPC API delegate for plugin", "provider", interfaceName, "namespace", namespace) + if delegates, err := pluginProvider.apiProviderFunc(namespace, s); err != nil { + log.Error("unable to delegate RPC API calls to plugin", "provider", interfaceName, "error", err) + } else { + apis = append(apis, delegates...) + } + } + } + } + return apis +} + +func NewPluginManager(nodeName string, settings *Settings, skipVerify bool, localVerify bool, publicKey string) (*PluginManager, error) { + pm := &PluginManager{ + nodeName: nodeName, + pluginBaseDir: settings.BaseDir.String(), + centralClient: NewPluginCentralClient(settings.CentralConfig), + plugins: make(map[PluginInterfaceName]managedPlugin), + initializedPlugins: make(map[PluginInterfaceName]managedPlugin), + settings: settings, + } + pm.downloader = NewDownloader(pm) + if skipVerify { + log.Warn("plugin: ignore integrity verification") + pm.verifier = NewNonVerifier() + } else { + var err error + if pm.verifier, err = NewVerifier(pm, localVerify, publicKey); err != nil { + return nil, err + } + } + for pluginName, pluginDefinition := range settings.Providers { + log.Debug("Preparing plugin", "provider", pluginName, "name", pluginDefinition.Name, "version", pluginDefinition.Version) + pluginProvider, ok := pluginProviders[pluginName] + if !ok { + return nil, fmt.Errorf("plugin: [%s] is not supported", pluginName) + } + base, err := newBasePlugin(pm, pluginName, pluginDefinition, pluginProvider.pluginSet) + if err != nil { + return nil, fmt.Errorf("plugin [%s] %s", pluginName, err.Error()) + } + pm.initializedPlugins[pluginName] = base + } + return pm, nil +} + +func NewEmptyPluginManager() *PluginManager { + return &PluginManager{ + plugins: make(map[PluginInterfaceName]managedPlugin), + } +} diff --git a/plugin/service_test.go b/plugin/service_test.go new file mode 100644 index 0000000000..e6dbaa6564 --- /dev/null +++ b/plugin/service_test.go @@ -0,0 +1,144 @@ +package plugin + +import ( + "testing" + + "github.com/hashicorp/go-plugin" + testifyassert "github.com/stretchr/testify/assert" +) + +func typicalPluginManager(t *testing.T) *PluginManager { + testObject, err := NewPluginManager("arbitraryName", &Settings{ + Providers: map[PluginInterfaceName]PluginDefinition{ + HelloWorldPluginInterfaceName: { + Name: "arbitrary-helloWorld", + Version: "1.0.0", + Config: "arbitrary config", + }, + }, + }, false, false, "") + + testifyassert.NoError(t, err) + return testObject +} + +func TestPluginManager_ProvidersPopulation(t *testing.T) { + arbitraryPluginInterfaceName := PluginInterfaceName("arbitrary") + defer func() { + delete(pluginProviders, arbitraryPluginInterfaceName) + }() + pluginProviders[arbitraryPluginInterfaceName] = pluginProvider{ + pluginSet: plugin.PluginSet{}, + } + + testObject, err := NewPluginManager("arbitraryName", &Settings{ + Providers: map[PluginInterfaceName]PluginDefinition{ + HelloWorldPluginInterfaceName: { + Name: "arbitrary-helloWorld", + Version: "1.0.0", + Config: "arbitrary config", + }, + arbitraryPluginInterfaceName: { + Name: "foo-bar", + Version: "2.0.0", + Config: "arbitrary config", + }, + }, + }, false, false, "") + + testifyassert.NoError(t, err) + testifyassert.Equal(t, "arbitrary-helloWorld-1.0.0", testObject.initializedPlugins[HelloWorldPluginInterfaceName].(*basePlugin).pluginDefinition.FullName()) + testifyassert.Equal(t, "foo-bar-2.0.0", testObject.initializedPlugins[arbitraryPluginInterfaceName].(*basePlugin).pluginDefinition.FullName()) +} + +func TestPluginManager_GetPluginTemplate_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + testObject := typicalPluginManager(t) + + p := new(HelloWorldPluginTemplate) + err := testObject.GetPluginTemplate(HelloWorldPluginInterfaceName, p) + + assert.NoError(err) + assert.NotNil(p) +} + +func TestPluginManager_GetPlugin_whenReadFromCache(t *testing.T) { + assert := testifyassert.New(t) + testObject := typicalPluginManager(t) + p := new(HelloWorldPluginTemplate) + err := testObject.GetPluginTemplate(HelloWorldPluginInterfaceName, p) + assert.NoError(err) + assert.NotNil(p) + + actual, ok := testObject.getPlugin(HelloWorldPluginInterfaceName) + + assert.True(ok) + assert.Equal(p, actual) +} + +func TestPluginManager_GetPlugin_whenReadFromInitializedPluginsCache(t *testing.T) { + assert := testifyassert.New(t) + testObject := typicalPluginManager(t) + + actual, ok := testObject.getPlugin(HelloWorldPluginInterfaceName) + + assert.True(ok) + assert.IsType(new(basePlugin), actual) +} + +func TestPluginManager_GetPluginTemplate_whenReadFromCache(t *testing.T) { + assert := testifyassert.New(t) + testObject := typicalPluginManager(t) + p := new(HelloWorldPluginTemplate) + err := testObject.GetPluginTemplate(HelloWorldPluginInterfaceName, p) + assert.NoError(err) + assert.NotNil(p) + + actual := new(HelloWorldPluginTemplate) + err = testObject.GetPluginTemplate(HelloWorldPluginInterfaceName, actual) + + assert.NoError(err) + assert.Equal(p, actual) +} + +func TestPluginManager_GetPluginTemplate_whenPluginTemplateNotExtendBasePlugin(t *testing.T) { + assert := testifyassert.New(t) + testObject := typicalPluginManager(t) + + invalid := new(invalidPluginTemplate) + err := testObject.GetPluginTemplate(HelloWorldPluginInterfaceName, invalid) + + t.Log(err) + assert.Error(err) +} + +func TestPluginManager_GetPluginTemplate_whenPluginTemplateNotExtendPointerBasePlugin(t *testing.T) { + assert := testifyassert.New(t) + testObject := typicalPluginManager(t) + + invalid := new(invalidPluginTemplateNoPointer) + err := testObject.GetPluginTemplate(HelloWorldPluginInterfaceName, invalid) + + t.Log(err) + assert.Error(err) +} + +type invalidPluginTemplateNoPointer struct { + basePlugin +} + +type invalidPluginTemplate struct { + someField int +} + +func (i invalidPluginTemplate) Start() error { + panic("implement me") +} + +func (i invalidPluginTemplate) Stop() error { + panic("implement me") +} + +func (i invalidPluginTemplate) Info() (PluginInterfaceName, interface{}) { + panic("implement me") +} diff --git a/plugin/settings.go b/plugin/settings.go new file mode 100644 index 0000000000..aa033212fd --- /dev/null +++ b/plugin/settings.go @@ -0,0 +1,290 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + + "github.com/ethereum/go-ethereum/plugin/account" + "github.com/ethereum/go-ethereum/plugin/helloworld" + "github.com/ethereum/go-ethereum/plugin/security" + "github.com/ethereum/go-ethereum/rpc" + "github.com/hashicorp/go-plugin" + "github.com/naoina/toml" +) + +const ( + HelloWorldPluginInterfaceName = PluginInterfaceName("helloworld") // lower-case always + SecurityPluginInterfaceName = PluginInterfaceName("security") + AccountPluginInterfaceName = PluginInterfaceName("account") +) + +var ( + // define additional plugins being supported here + pluginProviders = map[PluginInterfaceName]pluginProvider{ + HelloWorldPluginInterfaceName: { + apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) { + template := new(HelloWorldPluginTemplate) + if err := pm.GetPluginTemplate(HelloWorldPluginInterfaceName, template); err != nil { + return nil, err + } + service, err := template.Get() + if err != nil { + return nil, err + } + return []rpc.API{{ + Namespace: ns, + Version: "1.0.0", + Service: service, + Public: true, + }}, nil + }, + pluginSet: plugin.PluginSet{ + helloworld.ConnectorName: &helloworld.PluginConnector{}, + }, + }, + SecurityPluginInterfaceName: { + pluginSet: plugin.PluginSet{ + security.TLSConfigurationConnectorName: &security.TLSConfigurationSourcePluginConnector{}, + security.AuthenticationConnectorName: &security.AuthenticationManagerPluginConnector{}, + }, + }, + AccountPluginInterfaceName: { + apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) { + f := new(ReloadableAccountServiceFactory) + if err := pm.GetPluginTemplate(AccountPluginInterfaceName, f); err != nil { + return nil, err + } + service, err := f.Create() + if err != nil { + return nil, err + } + return []rpc.API{{ + Namespace: ns, + Version: "1.0.0", + Service: account.NewCreator(service), + Public: true, + }}, nil + }, + pluginSet: plugin.PluginSet{ + account.ConnectorName: &account.PluginConnector{}, + }, + }, + } + + // this is the place holder for future solution of the plugin central + quorumPluginCentralConfiguration = &PluginCentralConfiguration{ + CertFingerprint: "", + BaseURL: "https://dl.bintray.com/quorumengineering/quorum-plugins", + PublicKeyURI: "/.pgp/" + DefaultPublicKeyFile, + InsecureSkipTLSVerify: false, + } +) + +type pluginProvider struct { + // this allows exposing plugin interfaces to geth RPC API automatically. + // nil value implies that plugin won't expose its methods to geth RPC API + apiProviderFunc rpcAPIProviderFunc + // contains connectors being registered to the plugin library + pluginSet plugin.PluginSet +} + +type rpcAPIProviderFunc func(ns string, pm *PluginManager) ([]rpc.API, error) +type Version string + +// This is to describe a plugin +// +// Information is used to discover the plugin binary and verify its integrity +// before forking a process running the plugin +type PluginDefinition struct { + Name string `json:"name" toml:""` + // the semver version of the plugin + Version Version `json:"version" toml:""` + // plugin configuration in a form of map/slice/string + Config interface{} `json:"config,omitempty" toml:",omitempty"` +} + +func ReadMultiFormatConfig(config interface{}) ([]byte, error) { + if config == nil { + return []byte{}, nil + } + switch k := reflect.TypeOf(config).Kind(); k { + case reflect.Map, reflect.Slice: + return json.Marshal(config) + case reflect.String: + configStr := config.(string) + u, err := url.Parse(configStr) + if err != nil { // just return as is + return []byte(configStr), nil + } + switch s := u.Scheme; s { + case "file": + return ioutil.ReadFile(filepath.Join(u.Host, u.Path)) + case "env": // config string in an env variable + varName := u.Host + isFile := u.Query().Get("type") == "file" + if v, ok := os.LookupEnv(varName); ok { + if isFile { + return ioutil.ReadFile(v) + } else { + return []byte(v), nil + } + } else { + return nil, fmt.Errorf("env variable %s not found", varName) + } + default: + return []byte(configStr), nil + } + default: + return nil, fmt.Errorf("unsupported type of config [%s]", k) + } +} + +// return remote folder storing the plugin distribution file and signature file +// +// e.g.: my-plugin/v1.0.0/darwin-amd64 +func (m *PluginDefinition) RemotePath() string { + return fmt.Sprintf("%s/v%s/%s-%s", m.Name, m.Version, runtime.GOOS, runtime.GOARCH) +} + +// return plugin name and version +func (m *PluginDefinition) FullName() string { + return fmt.Sprintf("%s-%s", m.Name, m.Version) +} + +// return plugin distribution file name +func (m *PluginDefinition) DistFileName() string { + return fmt.Sprintf("%s.zip", m.FullName()) +} + +// return plugin distribution signature file name +func (m *PluginDefinition) SignatureFileName() string { + return fmt.Sprintf("%s.sha256sum.asc", m.DistFileName()) +} + +// must be always be lowercase when define constants +// as when unmarshaling from config, value will be case-lowered +type PluginInterfaceName string + +// When this is used as a key in map. This function is not invoked. +func (p *PluginInterfaceName) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *p = PluginInterfaceName(strings.ToLower(v)) + return nil +} + +func (p *PluginInterfaceName) UnmarshalTOML(data []byte) error { + var v string + if err := toml.Unmarshal(data, &v); err != nil { + return err + } + *p = PluginInterfaceName(strings.ToLower(v)) + return nil +} + +func (p *PluginInterfaceName) UnmarshalText(data []byte) error { + *p = PluginInterfaceName(strings.ToLower(string(data))) + return nil +} + +func (p PluginInterfaceName) String() string { + return string(p) +} + +// this defines plugins used in the geth node +type Settings struct { + BaseDir EnvironmentAwaredValue `json:"baseDir" toml:""` + CentralConfig *PluginCentralConfiguration `json:"central" toml:"Central"` + Providers map[PluginInterfaceName]PluginDefinition `json:"providers" toml:""` +} + +func (s *Settings) GetPluginDefinition(name PluginInterfaceName) (*PluginDefinition, bool) { + m, ok := s.Providers[name] + return &m, ok +} + +func (s *Settings) SetDefaults() { + if s.CentralConfig == nil { + s.CentralConfig = quorumPluginCentralConfiguration + } +} + +// CheckSettingsAreSupported validates Settings by ensuring that only supportedPlugins are defined. +// It is not required for all supportedPlugins to be defined. +// An error containing plugin details is returned if one or more unsupported plugins are defined. +func (s *Settings) CheckSettingsAreSupported(supportedPlugins []PluginInterfaceName) error { + errList := []PluginInterfaceName{} + for name := range s.Providers { + isValid := false + for _, supportedPlugin := range supportedPlugins { + if supportedPlugin == name { + isValid = true + break + } + } + if !isValid { + errList = append(errList, name) + } + } + if len(errList) != 0 { + return fmt.Errorf("unsupported plugins configured: %v", errList) + } + return nil +} + +type PluginCentralConfiguration struct { + // To implement certificate pinning while communicating with PluginCentral + // if it's empty, we skip cert pinning logic + CertFingerprint string `json:"certFingerprint" toml:""` + BaseURL string `json:"baseURL" toml:""` + PublicKeyURI string `json:"publicKeyURI" toml:""` + InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify" toml:""` +} + +// support URI format with 'env' scheme during JSON/TOML/TEXT unmarshalling +// e.g.: env://FOO_VAR means read a string value from FOO_VAR environment variable +type EnvironmentAwaredValue string + +func (d *EnvironmentAwaredValue) UnmarshalJSON(data []byte) error { + return d.unmarshal(data) +} + +func (d *EnvironmentAwaredValue) UnmarshalTOML(data []byte) error { + return d.unmarshal(data) +} + +func (d *EnvironmentAwaredValue) UnmarshalText(data []byte) error { + return d.unmarshal(data) +} + +func (d *EnvironmentAwaredValue) unmarshal(data []byte) error { + v := string(data) + isString := strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"") + if !isString { + return fmt.Errorf("not a string") + } + v = strings.TrimFunc(v, func(r rune) bool { + return r == '"' + }) + if u, err := url.Parse(v); err == nil { + switch u.Scheme { + case "env": + v = os.Getenv(u.Host) + } + } + *d = EnvironmentAwaredValue(v) + return nil +} + +func (d EnvironmentAwaredValue) String() string { + return string(d) +} diff --git a/plugin/settings_test.go b/plugin/settings_test.go new file mode 100644 index 0000000000..d280c73ee1 --- /dev/null +++ b/plugin/settings_test.go @@ -0,0 +1,276 @@ +package plugin + +import ( + "encoding/json" + "io/ioutil" + "os" + "regexp" + "testing" + + "github.com/ethereum/go-ethereum/plugin/account" + "github.com/naoina/toml" + testifyassert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReadMultiFormatConfig_whenConfigEmbeddedAsArray(t *testing.T) { + assert := testifyassert.New(t) + + av1 := "arbitrary value1" + av2 := "arbitrary value2" + + cfg, err := ReadMultiFormatConfig([]string{av1, av2}) + + assert.NoError(err) + assert.Contains(string(cfg), av1) + assert.Contains(string(cfg), av2) +} + +func TestReadMultiFormatConfig_whenConfigEmbeddedAsFile(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.Remove(tmpFile.Name()) + }() + av1 := "arbitrary value1" + _, err = tmpFile.WriteString(av1) + if err != nil { + t.Fatal(err) + } + t.Log("wrote tmp file: " + tmpFile.Name()) + assert := testifyassert.New(t) + + cfg, err := ReadMultiFormatConfig("file://" + tmpFile.Name()) + + assert.NoError(err) + assert.Equal(av1, string(cfg)) +} + +func TestReadMultiFormatConfig_whenConfigEmbeddedAsString(t *testing.T) { + av1 := "arbitrary value1" + assert := testifyassert.New(t) + + cfg, err := ReadMultiFormatConfig(av1) + + assert.NoError(err) + assert.Equal(av1, string(cfg)) +} + +func TestReadMultiFormatConfig_whenFromEnvVariable(t *testing.T) { + assert := testifyassert.New(t) + + arbitraryString := "arbitrary config string" + if err := os.Setenv("KEY1", arbitraryString); err != nil { + t.Fatal(err) + } + cfg, err := ReadMultiFormatConfig("env://KEY1") + + assert.NoError(err) + assert.Equal(arbitraryString, string(cfg)) +} + +func TestReadMultiFormatConfig_whenFromEnvFile(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.Remove(tmpFile.Name()) + }() + av1 := "arbitrary value1" + _, err = tmpFile.WriteString(av1) + if err != nil { + t.Fatal(err) + } + t.Log("wrote tmp file: " + tmpFile.Name()) + if err := os.Setenv("KEY1", tmpFile.Name()); err != nil { + t.Fatal(err) + } + + assert := testifyassert.New(t) + cfg, err := ReadMultiFormatConfig("env://KEY1?type=file") + + assert.NoError(err) + assert.Equal(av1, string(cfg)) +} + +func TestEnvironmentAwaredValue_UnmarshalJSON_whenValueFromEnvVariable(t *testing.T) { + assert := testifyassert.New(t) + + if err := os.Setenv("KEY1", "foo"); err != nil { + t.Fatal(err) + } + + var value struct { + Vinstance EnvironmentAwaredValue + Vpointer *EnvironmentAwaredValue + } + assert.NoError(json.Unmarshal([]byte(`{"Vinstance": "env://KEY1", "Vpointer": "env://KEY1"}`), &value)) + assert.Equal("foo", value.Vinstance.String()) + assert.Equal("foo", value.Vpointer.String()) +} + +func TestEnvironmentAwaredValue_UnmarshalJSON_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + var value struct { + Vinstance EnvironmentAwaredValue + Vpointer *EnvironmentAwaredValue + } + assert.NoError(json.Unmarshal([]byte(`{"Vinstance": "foo", "Vpointer": "bar"}`), &value)) + assert.Equal("foo", value.Vinstance.String()) + assert.Equal("bar", value.Vpointer.String()) +} + +func TestEnvironmentAwaredValue_UnmarshalTOML_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + var value struct { + Vinstance EnvironmentAwaredValue + Vpointer *EnvironmentAwaredValue + } + assert.NoError(toml.Unmarshal([]byte(` +Vinstance = "foo" +Vpointer = "bar"`), &value)) + assert.Equal("foo", value.Vinstance.String()) + assert.Equal("bar", value.Vpointer.String()) +} + +func TestEnvironmentAwaredValue_UnmarshalTOML_whenValueFromEnvVariable(t *testing.T) { + assert := testifyassert.New(t) + + if err := os.Setenv("KEY1", "foo"); err != nil { + t.Fatal(err) + } + + var value struct { + Vinstance EnvironmentAwaredValue + Vpointer *EnvironmentAwaredValue + } + assert.NoError(toml.Unmarshal([]byte(` +Vinstance = "env://KEY1" +Vpointer = "env://KEY1"`), &value)) + assert.Equal("foo", value.Vinstance.String()) + assert.Equal("foo", value.Vpointer.String()) +} + +func TestPluginInterfaceName_UnmarshalTOML_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + var value struct { + MyMap map[PluginInterfaceName]string + } + assert.NoError(toml.Unmarshal([]byte(` +[MyMap] +Foo = "a1" +BAR = "a2" +`), &value)) + assert.Contains(value.MyMap, PluginInterfaceName("foo")) + assert.Contains(value.MyMap, PluginInterfaceName("bar")) +} + +// For JSON, keys are not being changed. Might be a bug in the decoder +func TestPluginInterfaceName_UnmarshalJSON_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + var value struct { + MyMap map[PluginInterfaceName]string + } + assert.NoError(json.Unmarshal([]byte(` +{ + "MyMap": { + "Foo" : "a1", + "BAR" : "a2" + } +} +`), &value)) + assert.Contains(value.MyMap, PluginInterfaceName("foo")) + assert.Contains(value.MyMap, PluginInterfaceName("bar")) +} + +func TestAccountAPIProviderFunc_OnlyExposeAccountCreationAPI(t *testing.T) { + pm, err := NewPluginManager( + "arbitraryName", + &Settings{ + Providers: map[PluginInterfaceName]PluginDefinition{ + AccountPluginInterfaceName: { + Name: "arbitrary-account", + Version: "1.0.0", + Config: "arbitrary config", + }, + }, + }, + false, + false, + "", + ) + require.NoError(t, err) + + provider, ok := pluginProviders[AccountPluginInterfaceName] + require.True(t, ok) + + api, err := provider.apiProviderFunc("namespace", pm) + require.NoError(t, err) + require.Len(t, api, 1) + require.Equal(t, "namespace", api[0].Namespace) + require.Implements(t, (*account.CreatorService)(nil), api[0].Service) + + _, ok = api[0].Service.(account.Service) + require.False(t, ok) +} + +func TestSettings_CheckSettingsAreSupported_AllSupported(t *testing.T) { + s := Settings{ + Providers: map[PluginInterfaceName]PluginDefinition{ + AccountPluginInterfaceName: {}, + HelloWorldPluginInterfaceName: {}, + }, + } + supported := []PluginInterfaceName{AccountPluginInterfaceName, HelloWorldPluginInterfaceName} + + err := s.CheckSettingsAreSupported(supported) + + require.NoError(t, err) +} + +func TestSettings_CheckSettingsAreSupported_NoneSupported(t *testing.T) { + s := Settings{ + Providers: map[PluginInterfaceName]PluginDefinition{ + AccountPluginInterfaceName: {}, + HelloWorldPluginInterfaceName: {}, + }, + } + supported := []PluginInterfaceName{} + + err := s.CheckSettingsAreSupported(supported) + + require.Error(t, err) + + wantMsgPattern := regexp.MustCompile(`^unsupported plugins configured: \[(account|helloworld) (account|helloworld)\]$`) + matches := wantMsgPattern.FindStringSubmatch(err.Error()) + + // make sure the msg matches the pattern and the same plugin is not listed twice + require.Regexp(t, wantMsgPattern, err.Error()) + + require.NotNil(t, matches, "error message did not match wanted pattern") + require.Len(t, matches, 3) + require.NotEmpty(t, matches[1]) + require.NotEmpty(t, matches[2]) + require.NotEqualf(t, matches[1], matches[2], "\"%v\" listed twice", matches[1]) +} + +func TestSettings_CheckSettingsAreSupported_SomeSupported(t *testing.T) { + s := Settings{ + Providers: map[PluginInterfaceName]PluginDefinition{ + AccountPluginInterfaceName: {}, + HelloWorldPluginInterfaceName: {}, + }, + } + supported := []PluginInterfaceName{AccountPluginInterfaceName} + + err := s.CheckSettingsAreSupported(supported) + + require.EqualError(t, err, "unsupported plugins configured: [helloworld]") +} diff --git a/plugin/utils.go b/plugin/utils.go new file mode 100644 index 0000000000..10ec52a22b --- /dev/null +++ b/plugin/utils.go @@ -0,0 +1,173 @@ +package plugin + +import ( + "archive/zip" + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/url" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/log" + "github.com/pborman/uuid" + "golang.org/x/crypto/openpgp" +) + +// Returns true if provided string contains only alphanumeric characters with the exception of the character (.) otherwise false. +func isCleanFileName(s string) bool { + if s == "" { + return false + } + return regexp.MustCompile(`^[\w.-]+$`).MatchString(s) +} + +// Returns true if provided string contains only alphanumeric characters otherwise false +func isCleanEntryPoint(s string) bool { + if s == "" { + return false + } + return regexp.MustCompile(`^[\w-_.]+$`).MatchString(s) + +} + +func unzipFile(output string, input *zip.File) error { + inputFile, err := input.Open() + if err != nil { + return err + } + defer func() { + _ = inputFile.Close() + }() + outputFile, err := os.OpenFile( + output, + os.O_WRONLY|os.O_CREATE|os.O_TRUNC, + input.Mode(), + ) + if err != nil { + return err + } + defer func() { + _ = outputFile.Close() + }() + _, err = io.Copy(outputFile, inputFile) + return err +} + +// Unzip src path to dest. Creates dest if the file doesnt exists. +func unzip(src string, dest string) error { + zipReader, err := zip.OpenReader(src) + if err != nil { + return err + } + targetDir := dest + for _, file := range zipReader.Reader.File { + extractedFilePath := filepath.Join( + targetDir, + file.Name, + ) + if file.FileInfo().IsDir() { + if err := os.MkdirAll(extractedFilePath, file.Mode()); err != nil { + return err + } + } else { + if err := unzipFile(extractedFilePath, file); err != nil { + return err + } + } + } + return nil +} + +// Returns Hex encoded value of sha256(binary content of filepath) +func getSha256Checksum(filePath string) (string, error) { + //Open the passed argument and check for any error + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + //Open a new hash interface to write to + hash := sha256.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +// Unpack pluginPath and returns plugin random generated unpacking path & plugin metadata. +func unpackPlugin(pluginPath string) (string, *MetaData, error) { + // Unpack pluginMeta + // Reduce TOC/TOU risk + unpackDir := path.Join(os.TempDir(), uuid.New(), uuid.New()) + + err := os.MkdirAll(unpackDir, os.ModePerm) + if err != nil { + return unpackDir, nil, err + } + + // Unzip to new Dir + err = unzip(pluginPath, unpackDir) + if err != nil { + return unpackDir, nil, err + } + + // Make Plugin + pluginMeta := MetaData{} + // Verify Plugin Structure + jsonFile, err := os.Open(path.Join(unpackDir, "plugin-meta.json")) + if err != nil { + return unpackDir, nil, err + } + defer jsonFile.Close() + + if err := json.NewDecoder(jsonFile).Decode(&pluginMeta); err != nil { + return unpackDir, nil, err + } + + if pluginMeta.EntryPoint == "" { + return unpackDir, nil, fmt.Errorf("plugin-meta.json entry point not set") + } + + if !isCleanEntryPoint(pluginMeta.EntryPoint) { + return unpackDir, nil, fmt.Errorf("entrypoint must be only alphanumeric value") + } + return unpackDir, &pluginMeta, nil +} + +func verify(signature, pubkey []byte, checksum string) error { + // verify file signature + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(pubkey)) + if err != nil { + return err + } + entity, err := openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(checksum), bytes.NewReader(signature)) + if err != nil { + log.Debug("unable to verify signature with original checksum. Now add \\n to the end and try", "checksum", checksum, "error", err) + entity, err = openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(checksum+"\n"), bytes.NewReader(signature)) + if err != nil { + return err + } + } + if entity == nil { + return fmt.Errorf("verification failed") + } + return nil +} + +// resolve URL-based value to file path +func resolveFilePath(rawUrl string) (string, error) { + u, err := url.Parse(rawUrl) + if err != nil { + return "", err + } + return filepath.Abs(filepath.Join(u.Host, u.Path)) +} diff --git a/plugin/utils_test.go b/plugin/utils_test.go new file mode 100644 index 0000000000..2ce3e08ed6 --- /dev/null +++ b/plugin/utils_test.go @@ -0,0 +1,342 @@ +package plugin + +import ( + "archive/zip" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsValidTargetURL(t *testing.T) { + assert.Error(t, isValidTargetURL("https://localhost.com", "http://localhost.com")) + assert.Error(t, isValidTargetURL("https://localhost", "http://localhost.com")) + + if err := isValidTargetURL("http://localhost.com", "http://localhost.com"); err != nil { + t.Errorf(err.Error()) + } + + if err := isValidTargetURL("https://localhost.com/../../", "https://localhost.com"); err != nil { + t.Errorf(err.Error()) + } +} + +func TestIsCleanFileName(t *testing.T) { + assert.True(t, isCleanFileName("filename"), "filename is not valid") + assert.True(t, isCleanFileName("filename.exe"), "filename with .exe") + + assert.False(t, isCleanFileName(""), "filename is not valid") + assert.False(t, isCleanFileName("filename/"), "filename with /") + assert.False(t, isCleanFileName("filename\\u00"), "filename with \\") + assert.False(t, isCleanFileName("filename$"), "filename with $") + assert.False(t, isCleanFileName("filename%"), "filename with %") + assert.False(t, isCleanFileName("filename%00"), "filename with %") +} + +func TestIsCleanEntryPoint(t *testing.T) { + assert.True(t, isCleanEntryPoint("entrypoint"), "entrypoint is not valid") + assert.True(t, isCleanEntryPoint("entrypoint.exe"), "entrypoint with .exe") + + assert.False(t, isCleanEntryPoint(""), "entrypoint is not valid") + assert.False(t, isCleanEntryPoint("entrypoint/"), "entrypoint with /") + assert.False(t, isCleanEntryPoint("entrypoint\\u00"), "entrypoint with \\") + assert.False(t, isCleanEntryPoint("entrypoint$"), "entrypoint with $") + assert.False(t, isCleanEntryPoint("entrypoint%"), "entrypoint with %") + assert.False(t, isCleanEntryPoint("entrypoint%00"), "entrypoint with %") +} + +func TestResolveFilePath_whenTypical(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + f, err := ioutil.TempFile(tmpDir, "f-") + if err != nil { + t.Fatal(err) + } + actualFile, err := resolveFilePath("file://" + f.Name()) + + assert.NoError(t, err) + assert.Equal(t, f.Name(), actualFile) +} + +func TestResolveFilePath_whenInvalidFileURI(t *testing.T) { + _, err := resolveFilePath("://arbitrary non uri") + + assert.Error(t, err) +} + +func TestVerify_whenTypicalWithBintraySigner(t *testing.T) { + + err := verify(validSignatureSignedByBintray, bintrayPublicKey, arbitrarySHA256checksum) + + assert.NoError(t, err) +} + +func TestVerify_whenTypicalWithStandardSigner(t *testing.T) { + + err := verify(validSignature, signerPubKey, arbitraryChecksum) + + assert.NoError(t, err) +} + +func TestVerify_whenInvalid(t *testing.T) { + err := verify(validSignature, arbitraryPubKey, arbitraryChecksum) + + assert.Error(t, err) +} + +func TestUnpackPlugin_whenTypical(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + tmpZipFile, err := createArbitraryZip(tmpDir) + if err != nil { + t.Fatal(err) + } + + workspace, meta, err := unpackPlugin(tmpZipFile) + + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(workspace) + }() + assert.NotEmpty(t, workspace) + assert.NotNil(t, meta) +} + +func createArbitraryZip(tmpDir string) (string, error) { + tmpFile, err := ioutil.TempFile(tmpDir, "f-") + if err != nil { + return "", err + } + + // Create a new zip archive. + w := zip.NewWriter(tmpFile) + defer func() { + _ = w.Close() + }() + + // Add some files to the archive. + var files = []struct { + Name, Body string + }{ + {"readme.txt", "This archive contains some text files."}, + {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"}, + {"plugin-meta.json", ` +{ + "name": "arbitrary-plugin", + "version": "1.0.0", + "entrypoint": "echo", + "parameters": [ + "hello world" + ] +} +`}, + } + for _, file := range files { + f, err := w.Create(file.Name) + if err != nil { + return "", err + } + _, err = f.Write([]byte(file.Body)) + if err != nil { + return "", err + } + } + + return tmpFile.Name(), nil +} + +var ( + arbitraryChecksum = "bf9a942afca462a9fb45f471f8d4db8c79cf332d" + arbitrarySHA256checksum = "697dc791f0df55fbb86a7d985d29f23feff69e41681816a2c17352dcf10e693d" + // signature of the signed arbitrarySHA256checksum + validSignatureSignedByBintray = []byte(` +-----BEGIN PGP SIGNATURE----- +Version: BCPG v1.53 + +iQIcBAABCAAGBQJeZ7h7AAoJEDec4ZLUAathdxYP/jKPYxFlWiI0520SU5zFTfx4 +F6fQL4d0uGsg/xlxDQbiYP+3aNAMuPzmDAJtu0qn8HnG51uSBJ95YWjgvivE2sw3 +xVK9vsAEjkQRa3yMBgBCrtlfyaYz/URbzEiVU8BGUFusnohx1kh6Ak7SO8S7bsbk +LIiKtcVs5RqhTwQBOu8SP4pROeRlbLjJ99WLUjKl8l8Vy753ov0J8ohsFIOGgiou +8UAHAnqxYuUwkZ8hPLUzdL1GxR4zo9XK6ll1XayTDjVrKsM2MFM9lbLgeXnb26pj +VY9M3WixwaSS5bZOqwNYJYV4YVnIOiS9gplvyPI4joRjgXWVRgm1KAoZ20JCJ5sn +SILRjaUYuNk/rHxPeVtNTTO5GD9iroroj5DKLh7H9qZMwZ/3/d+3rFlzFicS18cH +kItSO0raRfMD7PT6+m6q/Ss/Ssx8TBbbKE7IbSPNoab13VYPLx2pN0Z0gARozTe3 +1yrtPmqUyJw/R96UXWjQLIANXHkMi5X56az0RUK68ALMROGchptoXEAdOGLyx/lK +CbdmcD4bESihwjGJvMtFNqQaLAkAyYH8BJ2xSx0/DDJYnazMevxCLaJTgop9pCaf +i8wSXeyp0KquNgi7gWUOizVrE/Rzg0w3xCOPvwduwcLHFtdkGnG0rceU7axy8jf6 +CX+P2tVLyv6EMv0ej8Wm +=+Qxa +-----END PGP SIGNATURE----- +`) + bintrayPublicKey = []byte(` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +JESa2SNRfw== +=EI0Z +-----END PGP PUBLIC KEY BLOCK----- +`) + // signature of the signed arbitraryChecksum + validSignature = []byte(` +-----BEGIN PGP SIGNATURE----- + +iQJBBAEBCAArFiEEHGpboPTpUoYceZX2PIgUS38YTSgFAl1C/8ENHGFiY0B0ZXN0 +LmNvbQAKCRA8iBRLfxhNKHeBEACs14x1+UoVEVNVDNSJORsQy6nthHiwrb5l66dW +KPcEt96y7KXJObSF7TWfmGjIgQXmDnrwMY78bKcbWVK90siDwA0SajUwmwmCbCeC +nMTIza1a64KblJRVGal9D5EWLdAOuQkAV2tddyWMqdvv2ef46y+2zmoKE3bOQLXj +sCi5e8myuh5ottfrf5Tkxi7QHrWICxYjAMEUkvke/jbYUFi1787VnHZ8LDG1x5WN +yz3KysyaraMiOstk5PcACU+bsvEIXFppJsgx9eNqdyfQ0/oMzKlqlHhss/W5osyq +LeVY9dcMXUSNGmB6deJde93pv3kYnLarhEM5Ovm5BxYMyzudk9hUy3wXyb51EPaL +z/hYViGpBVSwKY6q47s8duXruOA0TzYu5jYmJd+CzqBkDbJfh7JG9iJkdG4Q30ui +D2wvTBJfz6wu1qYj0semX4l4ntpJ6OcIvD0BpP1wz3eC9rt+3RzrjVWPbVoTOyB0 +V7vVQPMJowoPvluIUP0eInc+jDue2Z/8DHjWDu1k4jmZbO7r/5Hib79JtA3LIGqq +CizH/cFWXLJAh6n7tFBREKgCgrsSQDIppdMFNc8GyRIh2qIkGexcWOBdiO6iU41t +anKh+gD3mcn637Hzn0p2AA0TK0D/HzPX9ZCwgGVyoQkXoMa1zWqzQ7QEbk8DmH9M +5rr3iw== +=4cZl +-----END PGP SIGNATURE----- +`) + signerPubKey = []byte(` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=8vbI +-----END PGP PUBLIC KEY BLOCK----- +`) + arbitraryPubKey = []byte(` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBF1DABYBCADaEO4PuQjDl91EMSClZeGT6ohkf99BuJpLd+Qfpj5rnJEFwxEr +CiykzwQv3vaJR48NrEe2Sa4U6iqGKzI0maDZrWFi8q/4j2hFO4QM8Sa2IWAZKeDa +FKR+csrYX0f1PbiTspr+XjdvYKZtaOOs2qkFo5qOscN2rU7rLtK+NBDUR8sx+wDP +YI0+B0EpkQ0zSB/se918i2APpleqCXL15E3Ie1u+pBdgLiD5ZN1/iE5Tf+lPCcUT +O/stDXzlqz06zVwtSfsX381rz1r+wCsOsTQvpd8d7ztYyMnUwwEOF3b4FukeM+pw +TZHfF8yzjSD5rkYMlL4zP4SpROxyJooNApPbABEBAAG0IUFyYml0cmFyeUtleSA8 +YXJiaXRyYXJ5QHRlc3QuY29tPokBTgQTAQgAOBYhBCHupLgewVEnyziIcws53EZa +WzsXBQJdQwAWAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEAs53EZaWzsX +4RsIALTjYGwJpf5ZDAIx5v84miI7ArP3J3GFYQnBwIwpEmtKh0Y7RcJFNnHlt5Af +Xx4d2HrSxCBD9hiMecLfT73ZQDNLrUeIsv+UJb5JJe/JvQZyD1oWENVHLFzxDC70 +IJXEd+5goftcsGoXCz+tbk+NAvx1kRG3xbsx6PrKjU4w1d04aBrvcO8rCwYbd1lS +yOXWYtRJFK4rGlbReKH50onF65g2Sdzaqx6MPT1gPPjyi3LpuPeTypEdGwrK6eok +UqvhLOGiMMXh7s9t6UqfodQ6ayJsimmRw8+tIsSmlMy5cyhcmYQE3+VF2VfTmXv6 +ELLG0zVBWAeiDYo8AN7hy/MZM6q5AQ0EXUMAFgEIAMLrdMg/FHHFoZV8hv53Bsvd +Cr64kx1wxMW72rw3k1Onb4pXmoDNCkkTJqNX+9ocxkgf8eMUJKagKLjF/9c8M6oB +SfpL2XfI2WmY3HqhBE8p5WfXY819chH7qhzeDuy36q51CVngDJbl5sS1SIz2xMeC +BW+oqSXc59a6KQ+qXQg0iYUCIvfH+3Yi+wlmWQ1QQjKXGmmvJTR6vTx7pwX6awVd +HIBUw14A2xwC1uqHD+dC0GMNPTNlT1bP/SJ8F/W8uQxadCFyhjEaFWqnAFSpNwT8 +mG98DumXbjfhgKPVbSt9uBbMFUXfKpj6uECgvtqVfCpHlQf/tze7gpNsnKgEvdcA +EQEAAYkBNgQYAQgAIBYhBCHupLgewVEnyziIcws53EZaWzsXBQJdQwAWAhsMAAoJ +EAs53EZaWzsXkW4H/RMaBVx3cR+GQMLJ38MxRuIksV6Fi46AGJJeIp9vqNgYQBdq +J3pyPtW0rnBLuqRTZZ+cQOp9mDuaqrblqD9jRm8vKL6vhzRmS1affHD4NPhh1WKH +Avi26TEFE1Y/xQ630mcm5K8CF3ItKqO56MSALzpNdc6tDdDflNd7JhkC6iSVKjaE +BCR8hH8opNGta0cX0isOVLN1z1bRt/xJTOxjXqoJFcmIuHIOCQzk7ODvqyphaeuV +ys/n9RSyDZF01sXnU6LUWsHau5MdFmOZC5oPdWVjB6GIEpZtIccrhm6vH12TVhA3 +ERUWZPUImhIpQS8TsuwLMkccr7OEXjUayamdBKw= +=1rdD +-----END PGP PUBLIC KEY BLOCK----- +`) +) diff --git a/plugin/verifier.go b/plugin/verifier.go new file mode 100644 index 0000000000..739875a717 --- /dev/null +++ b/plugin/verifier.go @@ -0,0 +1,45 @@ +package plugin + +import ( + "fmt" + "path" + + "github.com/ethereum/go-ethereum/log" +) + +// Plugin Integrity Verifier. +// Verifier works on the assumption an attacker can not compromise the integrity of geth running process. +type Verifier interface { + // verify plugin signature using checksum & pgp public key + VerifySignature(definition *PluginDefinition, checksum string) error +} + +type NonVerifier struct { +} + +func (*NonVerifier) VerifySignature(definition *PluginDefinition, checksum string) error { + return nil +} + +func NewNonVerifier() *NonVerifier { + return &NonVerifier{} +} + +func NewVerifier(pm *PluginManager, localVerify bool, publicKey string) (Verifier, error) { + log.Debug("using verifier", "local", localVerify) + pluginBaseDir := pm.pluginBaseDir + centralClient := pm.centralClient + // resolve public key + if publicKey == "" { + publicKey = fmt.Sprintf("file://%s", path.Join(pluginBaseDir, DefaultPublicKeyFile)) + } + publicKeyPath, err := resolveFilePath(publicKey) + if err != nil { + return nil, err + } + if localVerify { + return NewLocalVerifier(publicKeyPath, pluginBaseDir) + } else { + return NewOnlineVerifier(centralClient), nil + } +} diff --git a/plugin/verifier_test.go b/plugin/verifier_test.go new file mode 100644 index 0000000000..6df62159e4 --- /dev/null +++ b/plugin/verifier_test.go @@ -0,0 +1,40 @@ +package plugin + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewVerifier_whenResolvingDefaultPublicKeyLocation(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "q-") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + if err := ioutil.WriteFile(path.Join(tmpDir, DefaultPublicKeyFile), []byte("foo"), 0644); err != nil { + t.Fatal(err) + } + arbitraryPM := &PluginManager{ + pluginBaseDir: tmpDir, + } + + testObject, err := NewVerifier(arbitraryPM, true, "") + + assert.NoError(t, err) + assert.IsType(t, &LocalVerifier{}, testObject) +} + +func TestNewVerifier_whenUsingOnlineVerifier(t *testing.T) { + arbitraryPM := &PluginManager{} + + testObject, err := NewVerifier(arbitraryPM, false, "") + + assert.NoError(t, err) + assert.IsType(t, &OnlineVerifier{}, testObject) +} diff --git a/private/cache/cache.go b/private/cache/cache.go new file mode 100644 index 0000000000..fe09b90df0 --- /dev/null +++ b/private/cache/cache.go @@ -0,0 +1,22 @@ +package cache + +import ( + "time" + + "github.com/ethereum/go-ethereum/private/engine" + gocache "github.com/patrickmn/go-cache" +) + +const ( + DefaultExpiration = 5 * time.Minute + CleanupInterval = 5 * time.Minute +) + +func NewDefaultCache() *gocache.Cache { + return gocache.New(DefaultExpiration, CleanupInterval) +} + +type PrivateCacheItem struct { + Payload []byte + Extra engine.ExtraMetadata +} diff --git a/private/engine/common.go b/private/engine/common.go new file mode 100644 index 0000000000..c08347f4b5 --- /dev/null +++ b/private/engine/common.go @@ -0,0 +1,107 @@ +package engine + +import ( + "errors" + "fmt" + "net/http" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + ErrPrivateTxManagerNotinUse = errors.New("private transaction manager is not in use") + 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") +) + +// Additional information for the private transaction that Private Transaction Manager carries +type ExtraMetadata struct { + // Hashes of affected Contracts + ACHashes common.EncryptedPayloadHashes + // Root Hash of a Merkle Trie containing all affected contract account in state objects + ACMerkleRoot common.Hash + // Privacy flag for contract: standardPrivate, partyProtection, psv + PrivacyFlag PrivacyFlagType + // Contract participants that are managed by the corresponding Tessera. + // Being used in Multi Tenancy + ManagedParties []string + // the sender of the transaction + Sender string +} + +type Client struct { + HttpClient *http.Client + BaseURL string +} + +func (c *Client) FullPath(path string) string { + return fmt.Sprintf("%s%s", c.BaseURL, path) +} + +func (c *Client) Get(path string) (*http.Response, error) { + response, err := c.HttpClient.Get(c.FullPath(path)) + if err != nil { + return response, fmt.Errorf("unable to submit request (method:%s,path:%s). Cause: %v", "GET", path, err) + } + return response, err +} + +type PrivacyFlagType uint64 + +const ( + PrivacyFlagStandardPrivate PrivacyFlagType = iota // 0 + PrivacyFlagPartyProtection PrivacyFlagType = 1 << PrivacyFlagType(iota-1) // 1 + PrivacyFlagStateValidation = iota | PrivacyFlagPartyProtection // 3 which includes PrivacyFlagPartyProtection +) + +func (f PrivacyFlagType) IsNotStandardPrivate() bool { + return !f.IsStandardPrivate() +} + +func (f PrivacyFlagType) IsStandardPrivate() bool { + return f == PrivacyFlagStandardPrivate +} + +func (f PrivacyFlagType) Has(other PrivacyFlagType) bool { + return other&f == other +} + +func (f PrivacyFlagType) HasAll(others ...PrivacyFlagType) bool { + var all PrivacyFlagType + for _, flg := range others { + all = all | flg + } + return f.Has(all) +} + +func (f PrivacyFlagType) Validate() error { + if f == PrivacyFlagStandardPrivate || f == PrivacyFlagPartyProtection || f == PrivacyFlagStateValidation { + return nil + } + return fmt.Errorf("invalid privacy flag") +} + +type PrivateTransactionManagerFeature uint64 + +const ( + None PrivateTransactionManagerFeature = iota // 0 + PrivacyEnhancements PrivateTransactionManagerFeature = 1 << PrivateTransactionManagerFeature(iota-1) // 1 + MultiTenancy PrivateTransactionManagerFeature = 1 << PrivateTransactionManagerFeature(iota-1) // 2 +) + +type FeatureSet struct { + features uint64 +} + +func NewFeatureSet(features ...PrivateTransactionManagerFeature) *FeatureSet { + var all uint64 = 0 + for _, feature := range features { + all = all | uint64(feature) + } + return &FeatureSet{features: all} +} + +func (p *FeatureSet) HasFeature(feature PrivateTransactionManagerFeature) bool { + return uint64(feature)&p.features != 0 +} diff --git a/private/engine/common_test.go b/private/engine/common_test.go new file mode 100644 index 0000000000..943caa2385 --- /dev/null +++ b/private/engine/common_test.go @@ -0,0 +1,71 @@ +package engine + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPrivacyFlag_whenTypical(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagPartyProtection | PrivacyFlagStateValidation + + assert.True(flag.Has(PrivacyFlagStateValidation)) +} + +func TestPrivacyFlag_whenCheckingMultipleFlags(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagPartyProtection + + assert.False(flag.Has(PrivacyFlagStateValidation | PrivacyFlagPartyProtection)) +} + +func TestPrivacyFlag_whenCheckingMultipleFlagsArray(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagStateValidation | PrivacyFlagPartyProtection + + assert.True(flag.HasAll(PrivacyFlagStateValidation, PrivacyFlagPartyProtection)) +} + +func TestPrivacyFlag_whenCheckingStandardPrivateFlag(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagStandardPrivate + + assert.True(flag.IsStandardPrivate()) +} + +func TestPrivacyFlag_whenCheckingNotStandardPrivateFlag(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagPartyProtection + + assert.True(flag.IsNotStandardPrivate()) +} + +func TestPrivacyFlag_whenPrivateStateValidation(t *testing.T) { + assert := assert.New(t) + + t.Logf("PrivateFlagStateValidation: %d", PrivacyFlagStateValidation) + + assert.True(PrivacyFlagStateValidation.Has(PrivacyFlagPartyProtection), "State Validation must have party protection by default") +} + +func TestPrivacyFlagType_Validate_whenSuccess(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagStateValidation + + assert.NoError(flag.Validate()) +} + +func TestPrivacyFlagType_Validate_whenFailure(t *testing.T) { + assert := assert.New(t) + + flag := PrivacyFlagType(4) + + assert.Error(flag.Validate()) +} diff --git a/private/engine/constellation/constellation.go b/private/engine/constellation/constellation.go new file mode 100644 index 0000000000..ef6dcd53ba --- /dev/null +++ b/private/engine/constellation/constellation.go @@ -0,0 +1,115 @@ +package constellation + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/private/engine" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private/cache" + + gocache "github.com/patrickmn/go-cache" +) + +type constellation struct { + node *Client + c *gocache.Cache +} + +func Is(ptm interface{}) bool { + _, ok := ptm.(*constellation) + return ok +} + +func New(client *engine.Client) *constellation { + return &constellation{ + node: &Client{ + httpClient: client.HttpClient, + }, + c: gocache.New(cache.DefaultExpiration, cache.CleanupInterval), + } +} + +func (g *constellation) Send(data []byte, from string, to []string, extra *engine.ExtraMetadata) (string, []string, common.EncryptedPayloadHash, error) { + if extra.PrivacyFlag.IsNotStandardPrivate() { + return "", nil, common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements + } + out, err := g.node.SendPayload(data, from, to, extra.ACHashes, extra.ACMerkleRoot) + if err != nil { + return "", nil, common.EncryptedPayloadHash{}, err + } + cacheKey := string(out.Bytes()) + g.c.Set(cacheKey, cache.PrivateCacheItem{ + Payload: data, + Extra: *extra, + }, cache.DefaultExpiration) + return "", nil, out, nil +} + +func (g *constellation) EncryptPayload(data []byte, from string, to []string, extra *engine.ExtraMetadata) ([]byte, error) { + return nil, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) DecryptPayload(payload common.DecryptRequest) ([]byte, *engine.ExtraMetadata, error) { + return nil, nil, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) { + return common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) SendSignedTx(data common.EncryptedPayloadHash, to []string, extra *engine.ExtraMetadata) (string, []string, []byte, error) { + return "", nil, nil, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) ReceiveRaw(data common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) { + return nil, "", nil, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) IsSender(txHash common.EncryptedPayloadHash) (bool, error) { + return false, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) GetParticipants(txHash common.EncryptedPayloadHash) ([]string, error) { + return nil, engine.ErrPrivateTxManagerNotSupported +} + +func (g *constellation) Receive(data common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) { + if common.EmptyEncryptedPayloadHash(data) { + return "", nil, nil, nil, nil + } + // Ignore this error since not being a recipient of + // a payload isn't an error. + // TODO: Return an error if it's anything OTHER than + // 'you are not a recipient.' + cacheKey := string(data.Bytes()) + x, found := g.c.Get(cacheKey) + if found { + cacheItem, ok := x.(cache.PrivateCacheItem) + if !ok { + return "", nil, nil, nil, fmt.Errorf("unknown cache item. expected type PrivateCacheItem") + } + return "", nil, cacheItem.Payload, &cacheItem.Extra, nil + } + privatePayload, acHashes, acMerkleRoot, err := g.node.ReceivePayload(data) + if nil != err { + return "", nil, nil, nil, err + } + extra := engine.ExtraMetadata{ + ACHashes: acHashes, + ACMerkleRoot: acMerkleRoot, + } + g.c.Set(cacheKey, cache.PrivateCacheItem{ + Payload: privatePayload, + Extra: extra, + }, cache.DefaultExpiration) + return "", nil, privatePayload, &extra, nil +} + +func (g *constellation) Name() string { + return "Constellation" +} + +func (g *constellation) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return false +} diff --git a/private/engine/constellation/node.go b/private/engine/constellation/node.go new file mode 100644 index 0000000000..21063047bc --- /dev/null +++ b/private/engine/constellation/node.go @@ -0,0 +1,77 @@ +package constellation + +import ( + "bytes" + "encoding/base64" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +type Client struct { + httpClient *http.Client +} + +func (c *Client) SendPayload(pl []byte, b64From string, b64To []string, acHashes common.EncryptedPayloadHashes, acMerkleRoot common.Hash) (common.EncryptedPayloadHash, error) { + method := "POST" + url := "http+unix://c/sendraw" + buf := bytes.NewBuffer(pl) + req, err := http.NewRequest(method, url, buf) + if err != nil { + return common.EncryptedPayloadHash{}, fmt.Errorf("unable to build request for (method:%s,path:%s). Cause: %v", method, url, err) + } + if b64From != "" { + req.Header.Set("c11n-from", b64From) + } + req.Header.Set("c11n-to", strings.Join(b64To, ",")) + req.Header.Set("Content-Type", "application/octet-stream") + res, err := c.httpClient.Do(req) + + if res != nil { + defer res.Body.Close() + } + if err != nil { + return common.EncryptedPayloadHash{}, fmt.Errorf("unable to submit request (method:%s,path:%s). Cause: %v", method, url, err) + } + if res.StatusCode != 200 { + return common.EncryptedPayloadHash{}, fmt.Errorf("Non-200 status code: %+v", res) + } + + hashBytes, err := ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body)) + if err != nil { + return common.EncryptedPayloadHash{}, fmt.Errorf("unable to decode response body for (method:%s,path:%s). Cause: %v", method, url, err) + } + return common.BytesToEncryptedPayloadHash(hashBytes), nil +} + +func (c *Client) ReceivePayload(key common.EncryptedPayloadHash) ([]byte, common.EncryptedPayloadHashes, common.Hash, error) { + method := "GET" + url := "http+unix://c/receiveraw" + req, err := http.NewRequest(method, url, nil) + if err != nil { + return nil, nil, common.Hash{}, fmt.Errorf("unable to build request for (method:%s,url:%s). Cause: %v", method, url, err) + } + req.Header.Set("c11n-key", key.ToBase64()) + res, err := c.httpClient.Do(req) + if err != nil { + return nil, nil, common.Hash{}, fmt.Errorf("unable to submit request (method:%s,url:%s). Cause: %v", method, url, err) + } + defer res.Body.Close() + + if res.StatusCode == 404 { // payload not found + return nil, nil, common.Hash{}, nil // empty payload + } + if res.StatusCode != 200 { + return nil, nil, common.Hash{}, fmt.Errorf("Non-200 status code: %+v", res) + } + + payload, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, nil, common.Hash{}, fmt.Errorf("unable to read response body for (method:%s,path:%s). Cause: %v", method, url, err) + } + + return payload, nil, common.Hash{}, nil +} diff --git a/private/engine/notinuse/notInUsePrivateTxManager.go b/private/engine/notinuse/notInUsePrivateTxManager.go new file mode 100644 index 0000000000..22c7190445 --- /dev/null +++ b/private/engine/notinuse/notInUsePrivateTxManager.go @@ -0,0 +1,59 @@ +package notinuse + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private/engine" +) + +var ErrPrivateTxManagerNotInUse = errors.New("private transaction manager is not in use") + +// NotInUsePrivateTxManager returns an error for all communication functions, +// stating that no private transaction manager is being used by the node +type PrivateTransactionManager struct{} + +func (ptm *PrivateTransactionManager) IsSender(txHash common.EncryptedPayloadHash) (bool, error) { + panic("implement me") +} + +func (ptm *PrivateTransactionManager) GetParticipants(txHash common.EncryptedPayloadHash) ([]string, error) { + panic("implement me") +} + +func (ptm *PrivateTransactionManager) Send(data []byte, from string, to []string, extra *engine.ExtraMetadata) (string, []string, common.EncryptedPayloadHash, error) { + return "", nil, common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerNotinUse +} + +func (ptm *PrivateTransactionManager) EncryptPayload(data []byte, from string, to []string, extra *engine.ExtraMetadata) ([]byte, error) { + return nil, engine.ErrPrivateTxManagerNotinUse +} + +func (ptm *PrivateTransactionManager) DecryptPayload(payload common.DecryptRequest) ([]byte, *engine.ExtraMetadata, error) { + return nil, nil, engine.ErrPrivateTxManagerNotSupported +} + +func (ptm *PrivateTransactionManager) StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) { + return common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerNotinUse +} + +func (ptm *PrivateTransactionManager) SendSignedTx(data common.EncryptedPayloadHash, to []string, extra *engine.ExtraMetadata) (string, []string, []byte, error) { + return "", nil, nil, engine.ErrPrivateTxManagerNotinUse +} + +func (ptm *PrivateTransactionManager) Receive(data common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) { + //error not thrown here, acts as though no private data to fetch + return "", nil, nil, nil, nil +} + +func (ptm *PrivateTransactionManager) ReceiveRaw(data common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) { + return nil, "", nil, engine.ErrPrivateTxManagerNotinUse +} + +func (ptm *PrivateTransactionManager) Name() string { + return "NotInUse" +} + +func (ptm *PrivateTransactionManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return false +} diff --git a/private/engine/notinuse/notInUsePrivateTxManager_test.go b/private/engine/notinuse/notInUsePrivateTxManager_test.go new file mode 100644 index 0000000000..e797177d20 --- /dev/null +++ b/private/engine/notinuse/notInUsePrivateTxManager_test.go @@ -0,0 +1,58 @@ +package notinuse + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/stretchr/testify/assert" +) + +func TestName(t *testing.T) { + ptm := &PrivateTransactionManager{} + name := ptm.Name() + + assert.Equal(t, name, "NotInUse", "got wrong name for NotInUsePrivateTxManager") +} + +func TestSendReturnsError(t *testing.T) { + ptm := &PrivateTransactionManager{} + + _, _, _, err := ptm.Send([]byte{}, "", []string{}, nil) + + assert.Equal(t, err, engine.ErrPrivateTxManagerNotinUse, "got wrong error in 'send'") +} + +func TestStoreRawReturnsError(t *testing.T) { + ptm := &PrivateTransactionManager{} + + _, err := ptm.StoreRaw([]byte{}, "") + + assert.Equal(t, err, engine.ErrPrivateTxManagerNotinUse, "got wrong error in 'storeraw'") +} + +func TestReceiveReturnsEmpty(t *testing.T) { + ptm := &PrivateTransactionManager{} + + _, _, data, metadata, err := ptm.Receive(common.EncryptedPayloadHash{}) + + assert.Nil(t, err, "got unexpected error in 'receive'") + assert.Nil(t, data, "got unexpected data in 'receive'") + assert.Nil(t, metadata, "got unexpected metadata in 'receive'") +} + +func TestReceiveRawReturnsError(t *testing.T) { + ptm := &PrivateTransactionManager{} + + _, _, _, err := ptm.ReceiveRaw(common.EncryptedPayloadHash{}) + + assert.Equal(t, err, engine.ErrPrivateTxManagerNotinUse, "got wrong error in 'send'") +} + +func TestSendSignedTxReturnsError(t *testing.T) { + ptm := &PrivateTransactionManager{} + + _, _, _, err := ptm.SendSignedTx(common.EncryptedPayloadHash{}, []string{}, nil) + + assert.Equal(t, err, engine.ErrPrivateTxManagerNotinUse, "got wrong error in 'SendSignedTx'") +} diff --git a/private/engine/tessera/model.go b/private/engine/tessera/model.go new file mode 100644 index 0000000000..a37b40d4b8 --- /dev/null +++ b/private/engine/tessera/model.go @@ -0,0 +1,94 @@ +package tessera + +import "github.com/ethereum/go-ethereum/private/engine" + +// request object for /send API +type sendRequest struct { + Payload []byte `json:"payload"` + + // base64-encoded + From string `json:"from,omitempty"` + + To []string `json:"to"` + + // Transactions' encrypted payload hashes for affected contracts + AffectedContractTransactions []string `json:"affectedContractTransactions"` + + // Merkle root for affected contracts + ExecHash string `json:"execHash,omitempty"` + + PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"` +} + +// request object for /send API +type storerawRequest struct { + Payload []byte `json:"payload"` + + // base64-encoded + From string `json:"from,omitempty"` +} + +// response object for /send API +type sendResponse struct { + // Base64-encoded + Key string `json:"key"` + // Public Keys + ManagedParties []string `json:"managedParties"` + // Sender tessera public key + SenderKey string `json:"senderKey"` +} + +type receiveResponse struct { + Payload []byte `json:"payload"` + + // Transactions' encrypted payload hashes for affected contracts + AffectedContractTransactions []string `json:"affectedContractTransactions"` + + // Merkle root for affected contracts + ExecHash string `json:"execHash"` + + PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"` + + // Public Keys + ManagedParties []string `json:"managedParties"` + // Sender tessera public key + SenderKey string `json:"senderKey"` +} + +type sendSignedTxRequest struct { + Hash []byte `json:"hash"` + To []string `json:"to"` + // Transactions' encrypted payload hashes for affected contracts + AffectedContractTransactions []string `json:"affectedContractTransactions"` + // Merkle root for affected contracts + ExecHash string `json:"execHash,omitempty"` + + PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"` +} + +type sendSignedTxResponse struct { + // Base64-encoded + Key string `json:"key"` + // Public Keys + ManagedParties []string `json:"managedParties"` + // Sender tessera public key + SenderKey string `json:"senderKey"` +} + +type encryptPayloadResponse struct { + SenderKey []byte `json:"senderKey"` + CipherText []byte `json:"cipherText"` + CipherTextNonce []byte `json:"cipherTextNonce"` + RecipientBoxes []string `json:"recipientBoxes"` + RecipientNonce []byte `json:"recipientNonce"` + RecipientKeys []string `json:"recipientKeys"` +} + +type decryptPayloadRequest struct { + SenderKey []byte `json:"senderKey"` + CipherText []byte `json:"cipherText"` + CipherTextNonce []byte `json:"cipherTextNonce"` + RecipientBoxes []string `json:"recipientBoxes"` + RecipientNonce []byte `json:"recipientNonce"` + RecipientKeys []string `json:"recipientKeys"` +} diff --git a/private/engine/tessera/tessera.go b/private/engine/tessera/tessera.go new file mode 100644 index 0000000000..73b40e4278 --- /dev/null +++ b/private/engine/tessera/tessera.go @@ -0,0 +1,449 @@ +package tessera + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/private/cache" + "github.com/ethereum/go-ethereum/private/engine" + gocache "github.com/patrickmn/go-cache" +) + +type tesseraPrivateTxManager struct { + features *engine.FeatureSet + client *engine.Client + cache *gocache.Cache +} + +func Is(ptm interface{}) bool { + _, ok := ptm.(*tesseraPrivateTxManager) + return ok +} + +func New(client *engine.Client, version []byte) *tesseraPrivateTxManager { + ptmVersion, err := parseVersion(version) + if err != nil { + log.Error(fmt.Sprintf("Error parsing version components from the tessera version: %s. Unable to extract transaction manager features.", version)) + } + return &tesseraPrivateTxManager{ + features: engine.NewFeatureSet(tesseraVersionFeatures(ptmVersion)...), + client: client, + cache: gocache.New(cache.DefaultExpiration, cache.CleanupInterval), + } +} + +func (t *tesseraPrivateTxManager) submitJSON(method, path string, request interface{}, response interface{}) (int, error) { + apiVersion := "" + if t.features.HasFeature(engine.MultiTenancy) { + apiVersion = "vnd.tessera-2.1+" + } + req, err := newOptionalJSONRequest(method, t.client.FullPath(path), request, apiVersion) + if err != nil { + return -1, fmt.Errorf("unable to build json request for (method:%s,path:%s). Cause: %v", method, path, err) + } + res, err := t.client.HttpClient.Do(req) + if err != nil { + return -1, fmt.Errorf("unable to submit request (method:%s,path:%s). Cause: %v", method, path, err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(res.Body) + return res.StatusCode, fmt.Errorf("%d status: %s", res.StatusCode, string(body)) + } + if err := json.NewDecoder(res.Body).Decode(response); err != nil { + return res.StatusCode, fmt.Errorf("unable to decode response body for (method:%s,path:%s). Cause: %v", method, path, err) + } + return res.StatusCode, nil +} + +func (t *tesseraPrivateTxManager) submitJSONOld(method, path string, request interface{}, response interface{}) (int, error) { + apiVersion := "" + req, err := newOptionalJSONRequest(method, t.client.FullPath(path), request, apiVersion) + if err != nil { + return -1, fmt.Errorf("unable to build json request for (method:%s,path:%s). Cause: %v", method, path, err) + } + res, err := t.client.HttpClient.Do(req) + if err != nil { + return -1, fmt.Errorf("unable to submit request (method:%s,path:%s). Cause: %v", method, path, err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(res.Body) + return res.StatusCode, fmt.Errorf("%d status: %s", res.StatusCode, string(body)) + } + if err := json.NewDecoder(res.Body).Decode(response); err != nil { + return res.StatusCode, fmt.Errorf("unable to decode response body for (method:%s,path:%s). Cause: %v", method, path, err) + } + return res.StatusCode, nil +} + +func (t *tesseraPrivateTxManager) Send(data []byte, from string, to []string, extra *engine.ExtraMetadata) (string, []string, common.EncryptedPayloadHash, error) { + if extra.PrivacyFlag.IsNotStandardPrivate() && !t.features.HasFeature(engine.PrivacyEnhancements) { + return "", nil, common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements + } + response := new(sendResponse) + acMerkleRoot := "" + if !common.EmptyHash(extra.ACMerkleRoot) { + acMerkleRoot = extra.ACMerkleRoot.ToBase64() + } + if _, err := t.submitJSON("POST", "/send", &sendRequest{ + Payload: data, + From: from, + To: to, + AffectedContractTransactions: extra.ACHashes.ToBase64s(), + ExecHash: acMerkleRoot, + PrivacyFlag: extra.PrivacyFlag, + }, response); err != nil { + return "", nil, common.EncryptedPayloadHash{}, err + } + + eph, err := common.Base64ToEncryptedPayloadHash(response.Key) + if err != nil { + return "", nil, common.EncryptedPayloadHash{}, fmt.Errorf("unable to decode encrypted payload hash: %s. Cause: %v", response.Key, err) + } + + cacheKey := eph.Hex() + t.cache.Set(cacheKey, cache.PrivateCacheItem{ + Payload: data, + Extra: engine.ExtraMetadata{ + ACHashes: extra.ACHashes, + ACMerkleRoot: extra.ACMerkleRoot, + PrivacyFlag: extra.PrivacyFlag, + ManagedParties: response.ManagedParties, + Sender: response.SenderKey, + }, + }, gocache.DefaultExpiration) + + return response.SenderKey, response.ManagedParties, eph, nil +} + +func (t *tesseraPrivateTxManager) EncryptPayload(data []byte, from string, to []string, extra *engine.ExtraMetadata) ([]byte, error) { + response := new(encryptPayloadResponse) + acMerkleRoot := "" + if !common.EmptyHash(extra.ACMerkleRoot) { + acMerkleRoot = extra.ACMerkleRoot.ToBase64() + } + + if _, err := t.submitJSON("POST", "/encodedpayload/create", &sendRequest{ + Payload: data, + From: from, + To: to, + AffectedContractTransactions: extra.ACHashes.ToBase64s(), + ExecHash: acMerkleRoot, + PrivacyFlag: extra.PrivacyFlag, + }, response); err != nil { + return nil, err + } + + output, _ := json.Marshal(response) + return output, nil +} + +func (t *tesseraPrivateTxManager) StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) { + + response := new(sendResponse) + + if _, err := t.submitJSON("POST", "/storeraw", &storerawRequest{ + Payload: data, + From: from, + }, response); err != nil { + return common.EncryptedPayloadHash{}, err + } + + eph, err := common.Base64ToEncryptedPayloadHash(response.Key) + if err != nil { + return common.EncryptedPayloadHash{}, fmt.Errorf("unable to decode encrypted payload hash: %s. Cause: %v", response.Key, err) + } + + cacheKey := eph.Hex() + var extra engine.ExtraMetadata + cacheKeyTemp := fmt.Sprintf("%s-incomplete", cacheKey) + t.cache.Set(cacheKeyTemp, cache.PrivateCacheItem{ + Payload: data, + Extra: extra, + }, gocache.DefaultExpiration) + + return eph, nil +} + +// allow new quorum to send raw transactions when connected to an old tessera +func (c *tesseraPrivateTxManager) sendSignedPayloadOctetStream(signedPayload []byte, b64To []string) (string, []string, []byte, error) { + buf := bytes.NewBuffer(signedPayload) + req, err := http.NewRequest("POST", c.client.FullPath("/sendsignedtx"), buf) + if err != nil { + return "", nil, nil, err + } + + req.Header.Set("c11n-to", strings.Join(b64To, ",")) + req.Header.Set("Content-Type", "application/octet-stream") + res, err := c.client.HttpClient.Do(req) + if err != nil { + return "", nil, nil, err + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return "", nil, nil, fmt.Errorf("Non-200 status code: %+v", res) + } + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", nil, nil, err + } + sender := "" + if len(res.Header["Tesserasender"]) > 0 { + sender = res.Header["Tesserasender"][0] + } + return sender, res.Header["Tesseramanagedparties"], data, nil +} + +// also populate cache item with additional extra metadata +func (t *tesseraPrivateTxManager) SendSignedTx(data common.EncryptedPayloadHash, to []string, extra *engine.ExtraMetadata) (string, []string, []byte, error) { + if extra.PrivacyFlag.IsNotStandardPrivate() && !t.features.HasFeature(engine.PrivacyEnhancements) { + return "", nil, nil, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements + } + response := new(sendSignedTxResponse) + acMerkleRoot := "" + if !common.EmptyHash(extra.ACMerkleRoot) { + acMerkleRoot = extra.ACMerkleRoot.ToBase64() + } + // The /sendsignedtx has been updated as part of privacy enhancements to support a json payload. + // If an older tessera is used - invoke the octetstream version of the /sendsignedtx + if t.features.HasFeature(engine.PrivacyEnhancements) { + if _, err := t.submitJSON("POST", "/sendsignedtx", &sendSignedTxRequest{ + Hash: data.Bytes(), + To: to, + AffectedContractTransactions: extra.ACHashes.ToBase64s(), + ExecHash: acMerkleRoot, + PrivacyFlag: extra.PrivacyFlag, + }, response); err != nil { + return "", nil, nil, err + } + } else { + sender, managedParties, returnedHash, err := t.sendSignedPayloadOctetStream(data.Bytes(), to) + if err != nil { + return "", nil, nil, err + } + response.Key = string(returnedHash) + response.ManagedParties = managedParties + response.SenderKey = sender + } + + hashBytes, err := base64.StdEncoding.DecodeString(response.Key) + if err != nil { + return "", nil, nil, err + } + // pull incomplete cache item and inject new cache item with complete information + cacheKey := data.Hex() + cacheKeyTemp := fmt.Sprintf("%s-incomplete", cacheKey) + if item, found := t.cache.Get(cacheKeyTemp); found { + if incompleteCacheItem, ok := item.(cache.PrivateCacheItem); ok { + t.cache.Set(cacheKey, cache.PrivateCacheItem{ + Payload: incompleteCacheItem.Payload, + Extra: engine.ExtraMetadata{ + ACHashes: extra.ACHashes, + ACMerkleRoot: extra.ACMerkleRoot, + PrivacyFlag: extra.PrivacyFlag, + ManagedParties: response.ManagedParties, + Sender: response.SenderKey, + }, + }, gocache.DefaultExpiration) + t.cache.Delete(cacheKeyTemp) + } + } + return response.SenderKey, response.ManagedParties, hashBytes, err +} + +func (t *tesseraPrivateTxManager) Receive(hash common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) { + return t.receive(hash, false) +} + +// retrieve raw will not return information about medata. +// Related to SendSignedTx +func (t *tesseraPrivateTxManager) ReceiveRaw(hash common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) { + sender, _, data, extra, err := t.receive(hash, true) + return data, sender, extra, err +} + +// retrieve raw will not return information about medata +func (t *tesseraPrivateTxManager) receive(data common.EncryptedPayloadHash, isRaw bool) (string, []string, []byte, *engine.ExtraMetadata, error) { + if common.EmptyEncryptedPayloadHash(data) { + return "", nil, nil, nil, nil + } + cacheKey := data.Hex() + if isRaw { + // indicate the cache item is incomplete, this will be fulfilled in SendSignedTx + cacheKey = fmt.Sprintf("%s-incomplete", cacheKey) + } + if item, found := t.cache.Get(cacheKey); found { + cacheItem, ok := item.(cache.PrivateCacheItem) + if !ok { + return "", nil, nil, nil, fmt.Errorf("unknown cache item. expected type PrivateCacheItem") + } + return cacheItem.Extra.Sender, cacheItem.Extra.ManagedParties, cacheItem.Payload, &cacheItem.Extra, nil + } + + response := new(receiveResponse) + if statusCode, err := t.submitJSON("GET", fmt.Sprintf("/transaction/%s?isRaw=%v", url.PathEscape(data.ToBase64()), isRaw), nil, response); err != nil { + if statusCode == http.StatusNotFound { + return "", nil, nil, nil, nil + } else { + return "", nil, nil, nil, err + } + } + var extra engine.ExtraMetadata + if !isRaw { + acHashes, err := common.Base64sToEncryptedPayloadHashes(response.AffectedContractTransactions) + if err != nil { + return "", nil, nil, nil, fmt.Errorf("unable to decode ACOTHs %v. Cause: %v", response.AffectedContractTransactions, err) + } + acMerkleRoot, err := common.Base64ToHash(response.ExecHash) + if err != nil { + return "", nil, nil, nil, fmt.Errorf("unable to decode execution hash %s. Cause: %v", response.ExecHash, err) + } + extra = engine.ExtraMetadata{ + ACHashes: acHashes, + ACMerkleRoot: acMerkleRoot, + PrivacyFlag: response.PrivacyFlag, + ManagedParties: response.ManagedParties, + Sender: response.SenderKey, + } + } + + t.cache.Set(cacheKey, cache.PrivateCacheItem{ + Payload: response.Payload, + Extra: extra, + }, gocache.DefaultExpiration) + + return response.SenderKey, response.ManagedParties, response.Payload, &extra, nil +} + +// retrieve raw will not return information about medata +func (t *tesseraPrivateTxManager) DecryptPayload(payload common.DecryptRequest) ([]byte, *engine.ExtraMetadata, error) { + response := new(receiveResponse) + if _, err := t.submitJSON("POST", "/encodedpayload/decrypt", &decryptPayloadRequest{ + SenderKey: payload.SenderKey, + CipherText: payload.CipherText, + CipherTextNonce: payload.CipherTextNonce, + RecipientBoxes: payload.RecipientBoxes, + RecipientNonce: payload.RecipientNonce, + RecipientKeys: payload.RecipientKeys, + }, response); err != nil { + return nil, nil, err + } + + var extra engine.ExtraMetadata + acHashes, err := common.Base64sToEncryptedPayloadHashes(response.AffectedContractTransactions) + if err != nil { + return nil, nil, fmt.Errorf("unable to decode ACOTHs %v. Cause: %v", response.AffectedContractTransactions, err) + } + acMerkleRoot, err := common.Base64ToHash(response.ExecHash) + if err != nil { + return nil, nil, fmt.Errorf("unable to decode execution hash %s. Cause: %v", response.ExecHash, err) + } + extra = engine.ExtraMetadata{ + ACHashes: acHashes, + ACMerkleRoot: acMerkleRoot, + PrivacyFlag: response.PrivacyFlag, + } + + return response.Payload, &extra, nil +} + +func (t *tesseraPrivateTxManager) IsSender(txHash common.EncryptedPayloadHash) (bool, error) { + requestUrl := "/transaction/" + url.PathEscape(txHash.ToBase64()) + "/isSender" + req, err := http.NewRequest("GET", t.client.FullPath(requestUrl), nil) + if err != nil { + return false, err + } + + res, err := t.client.HttpClient.Do(req) + + if res != nil { + defer res.Body.Close() + } + + if err != nil { + log.Error("Failed to get isSender from tessera", "err", err) + return false, err + } + + if res.StatusCode != 200 { + return false, fmt.Errorf("non-200 status code: %+v", res) + } + + out, err := ioutil.ReadAll(res.Body) + if err != nil { + return false, err + } + + return strconv.ParseBool(string(out)) +} + +func (t *tesseraPrivateTxManager) GetParticipants(txHash common.EncryptedPayloadHash) ([]string, error) { + requestUrl := "/transaction/" + url.PathEscape(txHash.ToBase64()) + "/participants" + req, err := http.NewRequest("GET", t.client.FullPath(requestUrl), nil) + if err != nil { + return nil, err + } + + res, err := t.client.HttpClient.Do(req) + + if res != nil { + defer res.Body.Close() + } + + if err != nil { + log.Error("Failed to get participants from tessera", "err", err) + return nil, err + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("Non-200 status code: %+v", res) + } + + out, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + split := strings.Split(string(out), ",") + + return split, nil +} + +func (t *tesseraPrivateTxManager) Name() string { + return "Tessera" +} + +func (t *tesseraPrivateTxManager) HasFeature(f engine.PrivateTransactionManagerFeature) bool { + return t.features.HasFeature(f) +} + +// don't serialize body if nil +func newOptionalJSONRequest(method string, path string, body interface{}, apiVersion string) (*http.Request, error) { + buf := new(bytes.Buffer) + if body != nil { + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + request, err := http.NewRequest(method, path, buf) + if err != nil { + return nil, err + } + request.Header.Set("User-Agent", fmt.Sprintf("quorum-v%s", params.QuorumVersion)) + request.Header.Set("Content-type", fmt.Sprintf("application/%sjson", apiVersion)) + request.Header.Set("Accept", fmt.Sprintf("application/%sjson", apiVersion)) + return request, nil +} diff --git a/private/engine/tessera/tessera_test.go b/private/engine/tessera/tessera_test.go new file mode 100644 index 0000000000..4c12cc73ea --- /dev/null +++ b/private/engine/tessera/tessera_test.go @@ -0,0 +1,451 @@ +package tessera + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/private/engine" + testifyassert "github.com/stretchr/testify/assert" +) + +var ( + emptyHash = common.EncryptedPayloadHash{} + arbitraryHash = common.BytesToEncryptedPayloadHash([]byte("arbitrary")) + arbitraryHash1 = common.BytesToEncryptedPayloadHash([]byte("arbitrary1")) + arbitraryNotFoundHash = common.BytesToEncryptedPayloadHash([]byte("not found")) + arbitraryHashNoPrivateMetadata = common.BytesToEncryptedPayloadHash([]byte("no private extra data")) + arbitraryPrivatePayload = []byte("arbitrary private payload") + arbitraryFrom = "arbitraryFrom" + arbitraryTo = []string{"arbitraryTo1", "arbitraryTo2"} + arbitraryPrivacyFlag = engine.PrivacyFlagPartyProtection + arbitraryExtra = &engine.ExtraMetadata{ + ACHashes: Must(common.Base64sToEncryptedPayloadHashes([]string{arbitraryHash.ToBase64()})).(common.EncryptedPayloadHashes), + ACMerkleRoot: common.StringToHash("arbitrary root hash"), + PrivacyFlag: arbitraryPrivacyFlag, + } + + testServer *httptest.Server + testObject *tesseraPrivateTxManager + + sendRequestCaptor = make(chan *capturedRequest) + receiveRequestCaptor = make(chan *capturedRequest) + sendSignedTxRequestCaptor = make(chan *capturedRequest) + sendSignedTxOctetStreamRequestCaptor = make(chan *capturedRequest) +) + +type capturedRequest struct { + err error + request interface{} + header http.Header +} + +func TestMain(m *testing.M) { + setup() + retCode := m.Run() + teardown() + os.Exit(retCode) +} + +func Must(o interface{}, err error) interface{} { + if err != nil { + panic(fmt.Sprintf("%s", err)) + } + return o +} + +func setup() { + mux := http.NewServeMux() + mux.HandleFunc("/send", MockSendAPIHandlerFunc) + mux.HandleFunc("/transaction/", MockReceiveAPIHandlerFunc) + mux.HandleFunc("/sendsignedtx", MockSendSignedTxAPIHandlerFunc) + + testServer = httptest.NewServer(mux) + + testObject = New(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }, []byte("2.0.0")) +} + +func MockSendAPIHandlerFunc(response http.ResponseWriter, request *http.Request) { + actualRequest := new(sendRequest) + if err := json.NewDecoder(request.Body).Decode(actualRequest); err != nil { + go func(o *capturedRequest) { sendRequestCaptor <- o }(&capturedRequest{err: err}) + } else { + go func(o *capturedRequest) { sendRequestCaptor <- o }(&capturedRequest{request: actualRequest, header: request.Header}) + data, _ := json.Marshal(&sendResponse{ + Key: arbitraryHash.ToBase64(), + ManagedParties: []string{"ArbitraryPublicKey"}, + }) + response.Write(data) + } +} + +func MockReceiveAPIHandlerFunc(response http.ResponseWriter, request *http.Request) { + path := string([]byte(request.RequestURI)[:strings.LastIndex(request.RequestURI, "?")]) + actualRequest, err := url.PathUnescape(strings.TrimPrefix(path, "/transaction/")) + if err != nil { + go func(o *capturedRequest) { sendRequestCaptor <- o }(&capturedRequest{err: err}) + } else { + go func(o *capturedRequest) { + receiveRequestCaptor <- o + }(&capturedRequest{request: actualRequest, header: request.Header}) + if actualRequest == arbitraryNotFoundHash.ToBase64() { + response.WriteHeader(http.StatusNotFound) + } else { + var data []byte + if actualRequest == arbitraryHashNoPrivateMetadata.ToBase64() { + data, _ = json.Marshal(&receiveResponse{ + Payload: arbitraryPrivatePayload, + ManagedParties: []string{"ArbitraryPublicKey"}, + }) + } else { + data, _ = json.Marshal(&receiveResponse{ + Payload: arbitraryPrivatePayload, + ExecHash: arbitraryExtra.ACMerkleRoot.ToBase64(), + AffectedContractTransactions: arbitraryExtra.ACHashes.ToBase64s(), + PrivacyFlag: arbitraryPrivacyFlag, + ManagedParties: []string{"ArbitraryPublicKey"}, + }) + } + response.Write(data) + } + } +} + +func MockSendSignedTxAPIHandlerFunc(response http.ResponseWriter, request *http.Request) { + actualRequest := new(sendSignedTxRequest) + if err := json.NewDecoder(request.Body).Decode(actualRequest); err != nil { + go func(o *capturedRequest) { sendSignedTxRequestCaptor <- o }(&capturedRequest{err: err}) + } else { + go func(o *capturedRequest) { sendSignedTxRequestCaptor <- o }(&capturedRequest{request: actualRequest, header: request.Header}) + data, _ := json.Marshal(&sendSignedTxResponse{ + Key: arbitraryHash.ToBase64(), + }) + response.Write(data) + } +} + +func MockSendSignedTxOctetStreamAPIHandlerFunc(response http.ResponseWriter, request *http.Request) { + actualRequest := new(sendSignedTxRequest) + reqHash, err := ioutil.ReadAll(request.Body) + if err != nil { + go func(o *capturedRequest) { sendSignedTxOctetStreamRequestCaptor <- o }(&capturedRequest{err: err}) + return + } + actualRequest.Hash = reqHash + actualRequest.To = strings.Split(request.Header["C11n-To"][0], ",") + + go func(o *capturedRequest) { sendSignedTxOctetStreamRequestCaptor <- o }(&capturedRequest{request: actualRequest, header: request.Header}) + response.Write([]byte(common.BytesToEncryptedPayloadHash(reqHash).ToBase64())) +} + +func teardown() { + testServer.Close() +} + +func verifyRequestHeader(h http.Header, t *testing.T) { + if h.Get("Content-type") != "application/json" { + t.Errorf("expected Content-type header is application/json") + } + + if h.Get("Accept") != "application/json" { + t.Errorf("expected Accept header is application/json") + } +} + +func verifyRequestHeaderMultiTenancy(h http.Header, t *testing.T) { + if h.Get("Content-type") != "application/vnd.tessera-2.1+json" { + t.Errorf("expected Content-type header is application/vnd.tessera-2.1+json") + } + + if h.Get("Accept") != "application/vnd.tessera-2.1+json" { + t.Errorf("expected Accept header is application/vnd.tessera-2.1+json") + } +} + +func TestSend_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + _, _, actualHash, err := testObject.Send(arbitraryPrivatePayload, arbitraryFrom, arbitraryTo, arbitraryExtra) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-sendRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeader(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(*sendRequest) + + assert.Equal(arbitraryPrivatePayload, actualRequest.Payload, "request.payload") + assert.Equal(arbitraryFrom, actualRequest.From, "request.from") + assert.Equal(arbitraryTo, actualRequest.To, "request.to") + assert.Equal(arbitraryPrivacyFlag, actualRequest.PrivacyFlag, "request.privacyFlag") + assert.Equal(arbitraryExtra.ACHashes.ToBase64s(), actualRequest.AffectedContractTransactions, "request.affectedContractTransactions") + assert.Equal(arbitraryExtra.ACMerkleRoot.ToBase64(), actualRequest.ExecHash, "request.execHash") + assert.Equal(arbitraryHash, actualHash, "returned hash") +} + +func TestSend_whenTypical_MultiTenancy(t *testing.T) { + assert := testifyassert.New(t) + + testObjectWithMT := New(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }, []byte("2.1")) + + _, _, actualHash, err := testObjectWithMT.Send(arbitraryPrivatePayload, arbitraryFrom, arbitraryTo, arbitraryExtra) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-sendRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeaderMultiTenancy(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(*sendRequest) + + assert.Equal(arbitraryPrivatePayload, actualRequest.Payload, "request.payload") + assert.Equal(arbitraryFrom, actualRequest.From, "request.from") + assert.Equal(arbitraryTo, actualRequest.To, "request.to") + assert.Equal(arbitraryPrivacyFlag, actualRequest.PrivacyFlag, "request.privacyFlag") + assert.Equal(arbitraryExtra.ACHashes.ToBase64s(), actualRequest.AffectedContractTransactions, "request.affectedContractTransactions") + assert.Equal(arbitraryExtra.ACMerkleRoot.ToBase64(), actualRequest.ExecHash, "request.execHash") + assert.Equal(arbitraryHash, actualHash, "returned hash") +} + +func TestSend_whenTesseraVersionDoesNotSupportPrivacyEnhancements(t *testing.T) { + assert := testifyassert.New(t) + + testObjectNoPE := New(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }, []byte("0.10-SNAPSHOT")) + + assert.False(testObjectNoPE.HasFeature(engine.PrivacyEnhancements), "the supplied version does not support privacy enhancements") + + // trying to send a party protection transaction + _, _, _, err := testObjectNoPE.Send(arbitraryPrivatePayload, arbitraryFrom, arbitraryTo, arbitraryExtra) + if err != engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements { + t.Fatal("Expecting send to raise ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements") + } +} + +func TestSendRaw_whenTesseraVersionDoesNotSupportPrivacyEnhancements(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + mux.HandleFunc("/send", MockSendAPIHandlerFunc) + mux.HandleFunc("/transaction/", MockReceiveAPIHandlerFunc) + mux.HandleFunc("/sendsignedtx", MockSendSignedTxOctetStreamAPIHandlerFunc) + + testServerNoPE := httptest.NewServer(mux) + defer testServerNoPE.Close() + + testObjectNoPE := New(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServerNoPE.URL, + }, []byte("0.10-SNAPSHOT")) + + assert.False(testObjectNoPE.HasFeature(engine.PrivacyEnhancements), "the supplied version does not support privacy enhancements") + + // trying to send a party protection transaction + _, _, _, err := testObjectNoPE.SendSignedTx(arbitraryHash, arbitraryTo, arbitraryExtra) + if err != engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements { + t.Fatal("Expecting send to raise ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements") + } + + // send a standard private transaction and check that the old version of the /sendsignedtx is used (using octetstream content type) + + // caching incomplete item + _, _, _, err = testObjectNoPE.ReceiveRaw(arbitraryHashNoPrivateMetadata) + if err != nil { + t.Fatalf("%s", err) + } + <-receiveRequestCaptor + + // caching complete item + _, _, _, err = testObjectNoPE.SendSignedTx(arbitraryHashNoPrivateMetadata, arbitraryTo, &engine.ExtraMetadata{ + PrivacyFlag: engine.PrivacyFlagStandardPrivate}) + if err != nil { + t.Fatalf("%s", err) + } + req := <-sendSignedTxOctetStreamRequestCaptor + assert.Equal("application/octet-stream", req.header["Content-Type"][0]) + + _, _, _, actualExtra, err := testObjectNoPE.Receive(arbitraryHashNoPrivateMetadata) + if err != nil { + t.Fatalf("%s", err) + } + assert.Equal(engine.PrivacyFlagStandardPrivate, actualExtra.PrivacyFlag, "cached privacy flag") + +} + +func TestReceive_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + _, _, _, actualExtra, err := testObject.Receive(arbitraryHash1) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-receiveRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeader(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(string) + + assert.Equal(arbitraryHash1.ToBase64(), actualRequest, "requested hash") + assert.Equal(arbitraryExtra.ACHashes, actualExtra.ACHashes, "returned affected contract transaction hashes") + assert.Equal(arbitraryExtra.ACMerkleRoot, actualExtra.ACMerkleRoot, "returned merkle root") + assert.Equal(arbitraryExtra.PrivacyFlag, actualExtra.PrivacyFlag, "returned privacy flag") +} + +func TestReceive_whenTypical_Multitenancy(t *testing.T) { + assert := testifyassert.New(t) + + testObjectWithMT := New(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }, []byte("2.1")) + + _, _, _, actualExtra, err := testObjectWithMT.Receive(arbitraryHash1) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-receiveRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeaderMultiTenancy(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(string) + + assert.Equal(arbitraryHash1.ToBase64(), actualRequest, "requested hash") + assert.Equal(arbitraryExtra.ACHashes, actualExtra.ACHashes, "returned affected contract transaction hashes") + assert.Equal(arbitraryExtra.ACMerkleRoot, actualExtra.ACMerkleRoot, "returned merkle root") + assert.Equal(arbitraryExtra.PrivacyFlag, actualExtra.PrivacyFlag, "returned privacy flag") +} + +func TestReceive_whenPayloadNotFound(t *testing.T) { + assert := testifyassert.New(t) + + _, _, data, _, err := testObject.Receive(arbitraryNotFoundHash) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-receiveRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeader(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(string) + + assert.Equal(arbitraryNotFoundHash.ToBase64(), actualRequest, "requested hash") + assert.Nil(data, "returned payload when not found") +} + +func TestReceive_whenEncryptedPayloadHashIsEmpty(t *testing.T) { + assert := testifyassert.New(t) + + _, _, data, _, err := testObject.Receive(emptyHash) + if err != nil { + t.Fatalf("%s", err) + } + assert.Empty(receiveRequestCaptor, "no request is actually sent") + assert.Nil(data, "returned payload when not found") +} + +func TestReceive_whenHavingPayloadButNoPrivateExtraMetadata(t *testing.T) { + assert := testifyassert.New(t) + + _, _, _, actualExtra, err := testObject.Receive(arbitraryHashNoPrivateMetadata) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-receiveRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeader(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(string) + + assert.Equal(arbitraryHashNoPrivateMetadata.ToBase64(), actualRequest, "requested hash") + assert.Empty(actualExtra.ACHashes, "returned affected contract transaction hashes") + assert.True(common.EmptyHash(actualExtra.ACMerkleRoot), "returned merkle root") +} + +func TestSendSignedTx_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + + _, _, _, err := testObject.SendSignedTx(arbitraryHash, arbitraryTo, arbitraryExtra) + if err != nil { + t.Fatalf("%s", err) + } + capturedRequest := <-sendSignedTxRequestCaptor + + if capturedRequest.err != nil { + t.Fatalf("%s", capturedRequest.err) + } + + verifyRequestHeader(capturedRequest.header, t) + + actualRequest := capturedRequest.request.(*sendSignedTxRequest) + + assert.Equal(arbitraryTo, actualRequest.To, "request.to") + assert.Equal(arbitraryExtra.ACHashes.ToBase64s(), actualRequest.AffectedContractTransactions, "request.affectedContractTransactions") + assert.Equal(arbitraryExtra.ACMerkleRoot.ToBase64(), actualRequest.ExecHash, "request.execHash") +} + +func TestReceive_whenCachingRawPayload(t *testing.T) { + assert := testifyassert.New(t) + + // caching incomplete item + _, _, _, err := testObject.ReceiveRaw(arbitraryHashNoPrivateMetadata) + if err != nil { + t.Fatalf("%s", err) + } + <-receiveRequestCaptor + + // caching complete item + _, _, _, err = testObject.SendSignedTx(arbitraryHashNoPrivateMetadata, arbitraryTo, arbitraryExtra) + if err != nil { + t.Fatalf("%s", err) + } + <-sendSignedTxRequestCaptor + + _, _, _, actualExtra, err := testObject.Receive(arbitraryHashNoPrivateMetadata) + if err != nil { + t.Fatalf("%s", err) + } + + assert.Equal(arbitraryExtra.ACHashes, actualExtra.ACHashes, "cached affected contract transaction hashes") + assert.Equal(arbitraryExtra.ACMerkleRoot, actualExtra.ACMerkleRoot, "cached merkle root") + assert.Equal(arbitraryExtra.PrivacyFlag, actualExtra.PrivacyFlag, "cached privacy flag") +} diff --git a/private/engine/tessera/tessera_version_checker.go b/private/engine/tessera/tessera_version_checker.go new file mode 100644 index 0000000000..16b0ec35a0 --- /dev/null +++ b/private/engine/tessera/tessera_version_checker.go @@ -0,0 +1,76 @@ +package tessera + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/ethereum/go-ethereum/private/engine" +) + +const versionLength = 3 + +type Version [versionLength]uint64 + +var ( + zero = Version{0, 0, 0} + privacyEnhancementsVersion = Version{2, 0, 0} + multitenancyVersion = Version{2, 1, 0} + + featureVersions = map[engine.PrivateTransactionManagerFeature]Version{ + engine.PrivacyEnhancements: privacyEnhancementsVersion, + engine.MultiTenancy: multitenancyVersion, + } +) + +func tesseraVersionFeatures(version Version) []engine.PrivateTransactionManagerFeature { + result := make([]engine.PrivateTransactionManagerFeature, 0) + for feature, featureVersion := range featureVersions { + if compareVersions(version, featureVersion) >= 0 { + result = append(result, feature) + } + } + return result +} + +// compare two versions +// if v1 > v2 - returns 1 +// if v1 < v2 - returns -1 +// if v1 = v2 - returns 0 +func compareVersions(v1, v2 Version) int { + for i := 0; i < versionLength; i++ { + if v1[i] > v2[i] { + return 1 + } else if v1[i] < v2[i] { + return -1 + } + } + return 0 +} + +// The tessera release versions have 3 components: major.mid.minor. +// Snapshot tessera builds may have versions made of 2 components: major.mid-SNAPSHOT. +// parseVersion will assume the minor version to be 0 for versions with only 2 components. +func parseVersion(version []byte) (res Version, err error) { + versionMajMidRegExp, _ := regexp.Compile(`([0-9]+)\.([0-9]+)([^0-9].*)?`) + versionMajMidMinRegExp, _ := regexp.Compile(`([0-9]+)\.([0-9]+)\.([0-9]+)([^0-9].*)?`) + + var submatch [][]byte + if versionMajMidMinRegExp.Match(version) { + submatch = versionMajMidMinRegExp.FindSubmatch(version)[1:4] + } else if versionMajMidRegExp.Match(version) { + submatch = versionMajMidRegExp.FindSubmatch(version)[1:3] + } else { + return zero, fmt.Errorf("input does not match the expected version pattern") + } + + // res should be initialized with {0,0,0} - thus it is ok for submatch to have a variable length of 2 or 3 + for idx, val := range submatch { + intVal, err := strconv.ParseUint(string(val), 10, 64) + if err != nil { + return zero, err + } + res[idx] = intVal + } + return res, nil +} diff --git a/private/engine/tessera/tessera_version_checker_test.go b/private/engine/tessera/tessera_version_checker_test.go new file mode 100644 index 0000000000..64e3d55c14 --- /dev/null +++ b/private/engine/tessera/tessera_version_checker_test.go @@ -0,0 +1,78 @@ +package tessera + +import ( + "testing" + + "github.com/ethereum/go-ethereum/private/engine" + "github.com/stretchr/testify/assert" +) + +func checkParseSucceeds(t *testing.T, version []byte, expectedVersion Version) { + parsedVersion, err := parseVersion(version) + if err != nil { + t.Errorf("unexpected error") + } + if compareVersions(parsedVersion, expectedVersion) != 0 { + t.Errorf("unexpected major or middle version missmatch") + } +} + +func TestParseVersion(t *testing.T) { + checkParseSucceeds(t, []byte("0.10.6"), Version{0, 10, 6}) + checkParseSucceeds(t, []byte("0.10-SNAPSHOT"), Version{0, 10, 0}) + checkParseSucceeds(t, []byte("0.10.1-SNAPSHOT"), Version{0, 10, 1}) + checkParseSucceeds(t, []byte("0.10.0-SNAPSHOT"), Version{0, 10, 0}) + checkParseSucceeds(t, []byte("0.11.12+12234"), Version{0, 11, 12}) + checkParseSucceeds(t, []byte("0.11-SNAPSHOT"), Version{0, 11, 0}) + // leading zeros in version components + checkParseSucceeds(t, []byte("000.0011-SNAPSHOT"), Version{0, 11, 0}) + + checkParseSucceeds(t, []byte("01.012 SNAPSHOT"), Version{1, 12, 0}) + + _, err := parseVersion([]byte("garbage")) + if err == nil { + t.Errorf("expecting error to be returned when garbage version is supplied") + } + + _, err = parseVersion([]byte("1.garbage")) + if err == nil { + t.Errorf("expecting error to be returned when garbage version is supplied") + } +} + +func TestVersionsComparison(t *testing.T) { + v1 := Version{1, 1, 1} + v2 := Version{1, 1, 1} + v3 := Version{2, 1, 1} + v4 := Version{1, 2, 1} + v5 := Version{1, 1, 2} + assert.Equal(t, 0, compareVersions(v1, v2), "versions should be equal") + assert.Equal(t, -1, compareVersions(v1, v3), "v1 shold be smaller than v3") + assert.Equal(t, 1, compareVersions(v3, v1), "v3 should be bigger than v1") + assert.Equal(t, -1, compareVersions(v1, v4), "v1 shold be smaller than v4") + assert.Equal(t, 1, compareVersions(v4, v1), "v4 should be bigger than v1") + assert.Equal(t, -1, compareVersions(v1, v5), "v1 shold be smaller than v5") + assert.Equal(t, 1, compareVersions(v5, v1), "v5 should be bigger than v1") +} + +func TestTesseraVersionFeatures(t *testing.T) { + res := tesseraVersionFeatures(Version{2, 11, 12}) + assert.Contains(t, res, engine.PrivacyEnhancements) + assert.Contains(t, res, engine.MultiTenancy) + res = tesseraVersionFeatures(Version{0, 12, 0}) + assert.NotContains(t, res, engine.PrivacyEnhancements) + assert.NotContains(t, res, engine.MultiTenancy) + res = tesseraVersionFeatures(Version{0, 11, 15}) + assert.NotContains(t, res, engine.PrivacyEnhancements) + assert.NotContains(t, res, engine.MultiTenancy) + res = tesseraVersionFeatures(Version{2, 0, 0}) + assert.Contains(t, res, engine.PrivacyEnhancements) + assert.NotContains(t, res, engine.MultiTenancy) + res = tesseraVersionFeatures(Version{2, 1, 1}) + assert.Contains(t, res, engine.PrivacyEnhancements) + assert.Contains(t, res, engine.MultiTenancy) + res = tesseraVersionFeatures(zero) + assert.NotContains(t, res, engine.PrivacyEnhancements) + assert.NotContains(t, res, engine.MultiTenancy) + assert.Empty(t, res) +} diff --git a/private/engine/tessera/tessera_version_reader.go b/private/engine/tessera/tessera_version_reader.go new file mode 100644 index 0000000000..3d5c7383c4 --- /dev/null +++ b/private/engine/tessera/tessera_version_reader.go @@ -0,0 +1,55 @@ +package tessera + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private/engine" +) + +const apiVersion1 = "1.0" + +// this method will be removed once quorum will implement a versioned tessera client (in line with tessera API versioning) +func RetrieveTesseraAPIVersion(client *engine.Client) string { + res, err := client.Get("/version/api") + if err != nil { + log.Error("Error invoking the tessera /version/api API:", "err", err) + return apiVersion1 + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + log.Error(fmt.Sprintf("Invalid status code returned by the tessera /version/api API: %d.", res.StatusCode)) + return apiVersion1 + } + var versions []string + if err := json.NewDecoder(res.Body).Decode(&versions); err != nil { + log.Error("Unable to deserialize the tessera response for /version/api API:", "err", err) + return apiVersion1 + } + if len(versions) == 0 { + log.Error("Expecting at least one API version to be returned by the tessera /version/api API.") + return apiVersion1 + } + // pick the latest version from the versions array + latestVersion := apiVersion1 + latestParsedVersion, _ := parseVersion([]byte(latestVersion)) + for _, ver := range versions { + if len(ver) == 0 { + log.Error("Invalid (empty) version returned by the tessera /version/api API. Skipping value.") + continue + } + parsedVer, err := parseVersion([]byte(ver)) + if err != nil { + log.Error(fmt.Sprintf("Unable to parse version returned by the tessera /version/api API: %s. Skipping value.", ver)) + continue + } + if compareVersions(parsedVer, latestParsedVersion) > 0 { + latestVersion = ver + latestParsedVersion = parsedVer + } + } + log.Info(fmt.Sprintf("Tessera API version: %s", latestVersion)) + return latestVersion +} diff --git a/private/engine/tessera/tessera_version_reader_test.go b/private/engine/tessera/tessera_version_reader_test.go new file mode 100644 index 0000000000..7ab1d7a8c4 --- /dev/null +++ b/private/engine/tessera/tessera_version_reader_test.go @@ -0,0 +1,121 @@ +package tessera + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/private/engine" + testifyassert "github.com/stretchr/testify/assert" +) + +func TestVersionApi_404NotFound(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + + testServer = httptest.NewServer(mux) + defer testServer.Close() + + version := RetrieveTesseraAPIVersion(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }) + + assert.Equal(apiVersion1, version) +} + +func TestVersionApi_GarbageData(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + mux.HandleFunc("/version/api", func(writer http.ResponseWriter, request *http.Request) { + writer.Write([]byte("GARBAGE")) + }) + + testServer = httptest.NewServer(mux) + defer testServer.Close() + + version := RetrieveTesseraAPIVersion(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }) + + assert.Equal(apiVersion1, version) +} + +func TestVersionApi_emptyVersionsArray(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + mux.HandleFunc("/version/api", func(writer http.ResponseWriter, request *http.Request) { + writer.Write([]byte("[]")) + }) + + testServer = httptest.NewServer(mux) + defer testServer.Close() + + version := RetrieveTesseraAPIVersion(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }) + + assert.Equal(apiVersion1, version) +} + +func TestVersionApi_invalidVersionItem(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + mux.HandleFunc("/version/api", func(writer http.ResponseWriter, request *http.Request) { + writer.Write([]byte("{\"versions\":[{\"version\":\"1.0\"},{}]}")) + }) + + testServer = httptest.NewServer(mux) + defer testServer.Close() + + version := RetrieveTesseraAPIVersion(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }) + + assert.Equal(apiVersion1, version) +} + +func TestVersionApi_validVersionInWrongOrder(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + mux.HandleFunc("/version/api", func(writer http.ResponseWriter, request *http.Request) { + writer.Write([]byte("[\"2.0\",\"1.0\"]")) + }) + + testServer = httptest.NewServer(mux) + defer testServer.Close() + + version := RetrieveTesseraAPIVersion(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }) + + assert.Equal("2.0", version) +} + +func TestVersionApi_validVersion(t *testing.T) { + assert := testifyassert.New(t) + + mux := http.NewServeMux() + mux.HandleFunc("/version/api", func(writer http.ResponseWriter, request *http.Request) { + writer.Write([]byte("[\"1.0\",\"2.0\"]")) + }) + + testServer = httptest.NewServer(mux) + defer testServer.Close() + + version := RetrieveTesseraAPIVersion(&engine.Client{ + HttpClient: &http.Client{}, + BaseURL: testServer.URL, + }) + + assert.Equal("2.0", version) +} diff --git a/private/private.go b/private/private.go new file mode 100644 index 0000000000..6856c2936d --- /dev/null +++ b/private/private.go @@ -0,0 +1,122 @@ +package private + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/common" + http2 "github.com/ethereum/go-ethereum/common/http" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/private/engine" + "github.com/ethereum/go-ethereum/private/engine/constellation" + "github.com/ethereum/go-ethereum/private/engine/notinuse" + "github.com/ethereum/go-ethereum/private/engine/tessera" +) + +var ( + // global variable to be accessed by other packages + // singleton gateway to interact with private transaction manager + P PrivateTransactionManager + isPrivacyEnabled = false +) + +type Identifiable interface { + Name() string + HasFeature(f engine.PrivateTransactionManagerFeature) bool +} + +// Interacting with Private Transaction Manager APIs +type PrivateTransactionManager interface { + Identifiable + + Send(data []byte, from string, to []string, extra *engine.ExtraMetadata) (string, []string, common.EncryptedPayloadHash, error) + StoreRaw(data []byte, from string) (common.EncryptedPayloadHash, error) + SendSignedTx(data common.EncryptedPayloadHash, to []string, extra *engine.ExtraMetadata) (string, []string, []byte, error) + // Returns nil payload if not found + Receive(data common.EncryptedPayloadHash) (string, []string, []byte, *engine.ExtraMetadata, error) + // Returns nil payload if not found + ReceiveRaw(data common.EncryptedPayloadHash) ([]byte, string, *engine.ExtraMetadata, error) + IsSender(txHash common.EncryptedPayloadHash) (bool, error) + GetParticipants(txHash common.EncryptedPayloadHash) ([]string, error) + EncryptPayload(data []byte, from string, to []string, extra *engine.ExtraMetadata) ([]byte, error) + DecryptPayload(payload common.DecryptRequest) ([]byte, *engine.ExtraMetadata, error) +} + +// This loads any config specified via the legacy environment variable +func GetLegacyEnvironmentConfig() (http2.Config, error) { + return FromEnvironmentOrNil("PRIVATE_CONFIG") +} + +func FromEnvironmentOrNil(name string) (http2.Config, error) { + cfgPath := os.Getenv(name) + cfg, err := http2.FetchConfigOrIgnore(cfgPath) + if err != nil { + return http2.Config{}, err + } + + return cfg, nil +} + +func InitialiseConnection(cfg http2.Config) error { + var err error + P, err = NewPrivateTxManager(cfg) + return err +} + +func IsQuorumPrivacyEnabled() bool { + return isPrivacyEnabled +} + +func NewPrivateTxManager(cfg http2.Config) (PrivateTransactionManager, error) { + + if cfg.ConnectionType == http2.NoConnection { + log.Info("Running with private transaction manager disabled - quorum private transactions will not be supported") + return ¬inuse.PrivateTransactionManager{}, nil + } + + client, err := http2.CreateClient(cfg) + if err != nil { + return nil, fmt.Errorf("unable to create connection to private tx manager due to: %s", err) + } + + ptm, err := selectPrivateTxManager(client) + if err != nil { + return nil, fmt.Errorf("unable to connect to private tx manager due to: %s", err) + } + + isPrivacyEnabled = true + return ptm, nil +} + +// First call /upcheck to make sure the private tx manager is up +// Then call /version to decide which private tx manager client implementation to be used +func selectPrivateTxManager(client *engine.Client) (PrivateTransactionManager, error) { + res, err := client.Get("/upcheck") + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, engine.ErrPrivateTxManagerNotReady + } + res, err = client.Get("/version") + if err != nil { + return nil, err + } + defer res.Body.Close() + version, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var privateTxManager PrivateTransactionManager + defer func() { + log.Info("Target Private Tx Manager", "name", privateTxManager.Name(), "distributionVersion", string(version)) + }() + if res.StatusCode != 200 { + // Constellation doesn't have /version endpoint + privateTxManager = constellation.New(client) + } else { + privateTxManager = tessera.New(client, []byte(tessera.RetrieveTesseraAPIVersion(client))) + } + return privateTxManager, nil +} diff --git a/private/private_test.go b/private/private_test.go new file mode 100644 index 0000000000..661f4c48c1 --- /dev/null +++ b/private/private_test.go @@ -0,0 +1,106 @@ +package private + +import ( + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "runtime" + "syscall" + "testing" + + http2 "github.com/ethereum/go-ethereum/common/http" + "github.com/ethereum/go-ethereum/private/engine/constellation" + "github.com/ethereum/go-ethereum/private/engine/tessera" + "github.com/stretchr/testify/assert" +) + +func TestFromEnvironmentOrNil_whenNoConfig(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("panic received: %s", r) + } + }() + os.Unsetenv("ARBITRARY_CONFIG_ENV") + cfg, err := FromEnvironmentOrNil("ARBITRARY_CONFIG_ENV") + + assert.NoError(t, err, "unexpected error") + assert.Equal(t, cfg.ConnectionType, http2.NoConnection, "expected no instance to be set") +} + +func TestFromEnvironmentOrNil_whenUsingUnixSocketWithConstellation(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("this test case is not supported for windows") + } + testServer, socketFile := startUnixSocketHTTPServer(t, map[string]http.HandlerFunc{ + "/upcheck": MockEmptySuccessHandler, + }) + defer testServer.Close() + defer func() { + if r := recover(); r != nil { + t.Errorf("panic received: %s", r) + } + }() + os.Setenv("ARBITRARY_CONFIG_ENV", socketFile) + cfg, err := FromEnvironmentOrNil("ARBITRARY_CONFIG_ENV") + assert.NoError(t, err, "unexpected error") + + p, err := NewPrivateTxManager(cfg) + assert.NoError(t, err, "unexpected error") + if !constellation.Is(p) { + t.Errorf("expected Constellation to be used but found %v", reflect.TypeOf(p)) + } +} + +func TestFromEnvironmentOrNil_whenUsingUnixSocketWithTessera(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("this test case is not supported for windows") + } + testServer, socketFile := startUnixSocketHTTPServer(t, map[string]http.HandlerFunc{ + "/upcheck": MockEmptySuccessHandler, + "/version": MockEmptySuccessHandler, + }) + defer testServer.Close() + defer func() { + if r := recover(); r != nil { + t.Errorf("panic received: %s", r) + } + }() + os.Setenv("ARBITRARY_CONFIG_ENV", socketFile) + cfg, err := FromEnvironmentOrNil("ARBITRARY_CONFIG_ENV") + assert.NoError(t, err, "unexpected error") + + p, err := NewPrivateTxManager(cfg) + assert.NoError(t, err, "unexpected error") + if !tessera.Is(p) { + t.Errorf("expected Tessera to be used but found %v", reflect.TypeOf(p)) + } +} + +func MockEmptySuccessHandler(_ http.ResponseWriter, _ *http.Request) { + +} + +func startUnixSocketHTTPServer(t *testing.T, handlers map[string]http.HandlerFunc) (*httptest.Server, string) { + tmpFile := filepath.Join(os.TempDir(), "temp.sock") + syscall.Unlink(tmpFile) + l, err := net.Listen("unix", tmpFile) + if err != nil { + t.Fatalf("can't start a unix socket server due to %s", err) + } + os.Chmod(tmpFile, 0600) + mux := http.NewServeMux() + for k, v := range handlers { + mux.HandleFunc(k, v) + } + + testServer := httptest.Server{ + Listener: l, + Config: &http.Server{Handler: mux}, + } + testServer.Start() + t.Log("Unix Socket HTTP server started") + return &testServer, tmpFile +} diff --git a/raft/api.go b/raft/api.go new file mode 100755 index 0000000000..f0641110b1 --- /dev/null +++ b/raft/api.go @@ -0,0 +1,134 @@ +package raft + +import ( + "errors" + + "github.com/coreos/etcd/pkg/types" +) + +type RaftNodeInfo struct { + ClusterSize int `json:"clusterSize"` + Role string `json:"role"` + Address *Address `json:"address"` + PeerAddresses []*Address `json:"peerAddresses"` + RemovedPeerIds []uint16 `json:"removedPeerIds"` + AppliedIndex uint64 `json:"appliedIndex"` + SnapshotIndex uint64 `json:"snapshotIndex"` +} + +type PublicRaftAPI struct { + raftService *RaftService +} + +func NewPublicRaftAPI(raftService *RaftService) *PublicRaftAPI { + return &PublicRaftAPI{raftService} +} + +func (s *PublicRaftAPI) Role() string { + if err := s.checkIfNodeInCluster(); err != nil { + return "" + } + _, err := s.raftService.raftProtocolManager.LeaderAddress() + if err != nil { + return "" + } + return s.raftService.raftProtocolManager.NodeInfo().Role +} + +// helper function to check if self node is part of cluster +func (s *PublicRaftAPI) checkIfNodeInCluster() error { + if s.raftService.raftProtocolManager.IsIDRemoved(uint64(s.raftService.raftProtocolManager.raftId)) { + return errors.New("node not part of raft cluster. operations not allowed") + } + return nil +} + +func (s *PublicRaftAPI) AddPeer(enodeId string) (uint16, error) { + if err := s.checkIfNodeInCluster(); err != nil { + return 0, err + } + return s.raftService.raftProtocolManager.ProposeNewPeer(enodeId, false) +} + +func (s *PublicRaftAPI) AddLearner(enodeId string) (uint16, error) { + if err := s.checkIfNodeInCluster(); err != nil { + return 0, err + } + return s.raftService.raftProtocolManager.ProposeNewPeer(enodeId, true) +} + +func (s *PublicRaftAPI) PromoteToPeer(raftId uint16) (bool, error) { + if err := s.checkIfNodeInCluster(); err != nil { + return false, err + } + return s.raftService.raftProtocolManager.PromoteToPeer(raftId) +} + +func (s *PublicRaftAPI) RemovePeer(raftId uint16) error { + if err := s.checkIfNodeInCluster(); err != nil { + return err + } + return s.raftService.raftProtocolManager.ProposePeerRemoval(raftId) +} + +func (s *PublicRaftAPI) Leader() (string, error) { + + addr, err := s.raftService.raftProtocolManager.LeaderAddress() + if err != nil { + return "", err + } + return addr.NodeId.String(), nil +} + +func (s *PublicRaftAPI) Cluster() ([]ClusterInfo, error) { + // check if the node has already been removed from cluster + // if yes return nil + if err := s.checkIfNodeInCluster(); err != nil { + return []ClusterInfo{}, nil + } + + nodeInfo := s.raftService.raftProtocolManager.NodeInfo() + if nodeInfo.Role == "" { + return []ClusterInfo{}, nil + } + + noLeader := false + leaderAddr, err := s.raftService.raftProtocolManager.LeaderAddress() + if err != nil { + noLeader = true + if s.raftService.raftProtocolManager.NodeInfo().Role == "" { + return []ClusterInfo{}, nil + } + } + + peerAddresses := append(nodeInfo.PeerAddresses, nodeInfo.Address) + clustInfo := make([]ClusterInfo, len(peerAddresses)) + for i, a := range peerAddresses { + role := "" + if !noLeader { + if a.RaftId == leaderAddr.RaftId { + role = "minter" + } else if s.raftService.raftProtocolManager.isLearner(a.RaftId) { + role = "learner" + } else if s.raftService.raftProtocolManager.isVerifier(a.RaftId) { + role = "verifier" + } + } + clustInfo[i] = ClusterInfo{*a, role, s.checkIfNodeIsActive(a.RaftId)} + } + return clustInfo, nil +} + +// checkIfNodeIsActive checks if the raft node is active +// if the raft node is active ActiveSince returns non-zero time +func (s *PublicRaftAPI) checkIfNodeIsActive(raftId uint16) bool { + if raftId == s.raftService.raftProtocolManager.raftId { + return true + } + activeSince := s.raftService.raftProtocolManager.transport.ActiveSince(types.ID(raftId)) + return !activeSince.IsZero() +} + +func (s *PublicRaftAPI) GetRaftId(enodeId string) (uint16, error) { + return s.raftService.raftProtocolManager.FetchRaftId(enodeId) +} diff --git a/raft/backend.go b/raft/backend.go new file mode 100644 index 0000000000..1731b42981 --- /dev/null +++ b/raft/backend.go @@ -0,0 +1,107 @@ +package raft + +import ( + "crypto/ecdsa" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +type RaftService struct { + blockchain *core.BlockChain + chainDb ethdb.Database // Block chain database + txMu sync.Mutex + txPool *core.TxPool + accountManager *accounts.Manager + downloader *downloader.Downloader + + raftProtocolManager *ProtocolManager + startPeers []*enode.Node + + // we need an event mux to instantiate the blockchain + eventMux *event.TypeMux + minter *minter + nodeKey *ecdsa.PrivateKey + calcGasLimitFunc func(block *types.Block) uint64 +} + +func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raftPort uint16, joinExisting bool, blockTime time.Duration, e *eth.Ethereum, startPeers []*enode.Node, datadir string, useDns bool) (*RaftService, error) { + service := &RaftService{ + eventMux: ctx.EventMux, + chainDb: e.ChainDb(), + blockchain: e.BlockChain(), + txPool: e.TxPool(), + accountManager: e.AccountManager(), + downloader: e.Downloader(), + startPeers: startPeers, + nodeKey: ctx.NodeKey(), + calcGasLimitFunc: e.CalcGasLimit, + } + + service.minter = newMinter(chainConfig, service, blockTime) + + var err error + if service.raftProtocolManager, err = NewProtocolManager(raftId, raftPort, service.blockchain, service.eventMux, startPeers, joinExisting, datadir, service.minter, service.downloader, useDns); err != nil { + return nil, err + } + + return service, nil +} + +// Backend interface methods: + +func (service *RaftService) AccountManager() *accounts.Manager { return service.accountManager } +func (service *RaftService) BlockChain() *core.BlockChain { return service.blockchain } +func (service *RaftService) ChainDb() ethdb.Database { return service.chainDb } +func (service *RaftService) DappDb() ethdb.Database { return nil } +func (service *RaftService) EventMux() *event.TypeMux { return service.eventMux } +func (service *RaftService) TxPool() *core.TxPool { return service.txPool } + +// node.Service interface methods: + +func (service *RaftService) Protocols() []p2p.Protocol { return []p2p.Protocol{} } +func (service *RaftService) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "raft", + Version: "1.0", + Service: NewPublicRaftAPI(service), + Public: true, + }, + } +} + +// Start implements node.Service, starting the background data propagation thread +// of the protocol. +func (service *RaftService) Start(p2pServer *p2p.Server) error { + service.raftProtocolManager.Start(p2pServer) + return nil +} + +// Stop implements node.Service, stopping the background data propagation thread +// of the protocol. +func (service *RaftService) Stop() error { + service.blockchain.Stop() + service.raftProtocolManager.Stop() + service.minter.stop() + service.eventMux.Stop() + + // chainDb.Close() handles gracefully if freezedb process is already stopped + service.chainDb.Close() + + log.Info("Raft stopped") + return nil +} diff --git a/raft/constants.go b/raft/constants.go new file mode 100644 index 0000000000..012df25d12 --- /dev/null +++ b/raft/constants.go @@ -0,0 +1,35 @@ +package raft + +import ( + etcdRaft "github.com/coreos/etcd/raft" +) + +const ( + //protocolName = "raft" + //protocolVersion uint64 = 0x01 + + //raftMsg = 0x00 + + minterRole = etcdRaft.LEADER + //verifierRole = etcdRaft.NOT_LEADER + + // Raft's ticker interval + tickerMS = 100 + + // We use a bounded channel of constant size buffering incoming messages + //msgChanSize = 1000 + + // Snapshot after this many raft messages + // + // TODO: measure and get this as low as possible without affecting performance + // + snapshotPeriod = 250 + + //peerUrlKeyPrefix = "peerUrl-" + + chainExtensionMessage = "Successfully extended chain" +) + +var ( + appliedDbKey = []byte("applied") +) diff --git a/raft/events.go b/raft/events.go new file mode 100644 index 0000000000..b2a9020d49 --- /dev/null +++ b/raft/events.go @@ -0,0 +1,13 @@ +package raft + +import ( + "github.com/ethereum/go-ethereum/core/types" +) + +type InvalidRaftOrdering struct { + // Current head of the chain + headBlock *types.Block + + // New block that should point to the head, but doesn't + invalidBlock *types.Block +} diff --git a/raft/handler.go b/raft/handler.go new file mode 100644 index 0000000000..aada74e676 --- /dev/null +++ b/raft/handler.go @@ -0,0 +1,1098 @@ +package raft + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "net/url" + "os" + "strconv" + "sync" + "time" + + "github.com/coreos/etcd/etcdserver/stats" + "github.com/coreos/etcd/pkg/fileutil" + raftTypes "github.com/coreos/etcd/pkg/types" + etcdRaft "github.com/coreos/etcd/raft" + "github.com/coreos/etcd/raft/raftpb" + "github.com/coreos/etcd/rafthttp" + "github.com/coreos/etcd/snap" + "github.com/coreos/etcd/wal" + mapset "github.com/deckarep/golang-set" + "github.com/syndtr/goleveldb/leveldb" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +type ProtocolManager struct { + mu sync.RWMutex // For protecting concurrent JS access to "local peer" and "remote peer" state + quitSync chan struct{} + stopped bool + + // Static configuration + joinExisting bool // Whether to join an existing cluster when a WAL doesn't already exist + bootstrapNodes []*enode.Node + raftId uint16 + raftPort uint16 + + // Local peer state (protected by mu vs concurrent access via JS) + address *Address + role int // Role: minter or verifier + appliedIndex uint64 // The index of the last-applied raft entry + snapshotIndex uint64 // The index of the latest snapshot. + + // Remote peer state (protected by mu vs concurrent access via JS) + leader uint16 + peers map[uint16]*Peer + removedPeers mapset.Set // *Permanently removed* peers + + // P2P transport + p2pServer *p2p.Server // Initialized in start() + useDns bool + + // Blockchain services + blockchain *core.BlockChain + downloader *downloader.Downloader + minter *minter + + // Blockchain events + eventMux *event.TypeMux + minedBlockSub *event.TypeMuxSubscription + + // Raft proposal events + blockProposalC chan *types.Block // for mined blocks to raft + confChangeProposalC chan raftpb.ConfChange // for config changes from js console to raft + + // Raft transport + unsafeRawNode etcdRaft.Node + transport *rafthttp.Transport + httpstopc chan struct{} + httpdonec chan struct{} + + // Raft snapshotting + snapshotter *snap.Snapshotter + snapdir string + confState raftpb.ConfState + + // Raft write-ahead log + waldir string + wal *wal.WAL + + // Storage + quorumRaftDb *leveldb.DB // Persistent storage for last-applied raft index + raftStorage *etcdRaft.MemoryStorage // Volatile raft storage +} + +var errNoLeaderElected = errors.New("no leader is currently elected") + +// +// Public interface +// + +func NewProtocolManager(raftId uint16, raftPort uint16, blockchain *core.BlockChain, mux *event.TypeMux, bootstrapNodes []*enode.Node, joinExisting bool, datadir string, minter *minter, downloader *downloader.Downloader, useDns bool) (*ProtocolManager, error) { + waldir := fmt.Sprintf("%s/raft-wal", datadir) + snapdir := fmt.Sprintf("%s/raft-snap", datadir) + quorumRaftDbLoc := fmt.Sprintf("%s/quorum-raft-state", datadir) + + manager := &ProtocolManager{ + bootstrapNodes: bootstrapNodes, + peers: make(map[uint16]*Peer), + leader: uint16(etcdRaft.None), + removedPeers: mapset.NewSet(), + joinExisting: joinExisting, + blockchain: blockchain, + eventMux: mux, + blockProposalC: make(chan *types.Block, 10), + confChangeProposalC: make(chan raftpb.ConfChange), + httpstopc: make(chan struct{}), + httpdonec: make(chan struct{}), + waldir: waldir, + snapdir: snapdir, + snapshotter: snap.New(snapdir), + raftId: raftId, + raftPort: raftPort, + quitSync: make(chan struct{}), + raftStorage: etcdRaft.NewMemoryStorage(), + minter: minter, + downloader: downloader, + useDns: useDns, + } + + if db, err := openQuorumRaftDb(quorumRaftDbLoc); err != nil { + return nil, err + } else { + manager.quorumRaftDb = db + } + + return manager, nil +} + +func (pm *ProtocolManager) Start(p2pServer *p2p.Server) { + log.Info("starting raft protocol handler") + + pm.p2pServer = p2pServer + pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{}) + pm.startRaft() + // update raft peers info to p2p server + pm.p2pServer.SetCheckPeerInRaft(pm.peerExist) + go pm.minedBroadcastLoop() +} + +func (pm *ProtocolManager) Stop() { + pm.mu.Lock() + defer pm.mu.Unlock() + + defer log.Info("raft protocol handler stopped") + + if pm.stopped { + return + } + + log.Info("stopping raft protocol handler...") + + for raftId, peer := range pm.peers { + pm.disconnectFromPeer(raftId, peer) + } + + pm.minedBlockSub.Unsubscribe() + + if pm.transport != nil { + pm.transport.Stop() + } + + close(pm.httpstopc) + <-pm.httpdonec + close(pm.quitSync) + + if pm.unsafeRawNode != nil { + pm.unsafeRawNode.Stop() + } + + pm.quorumRaftDb.Close() + + pm.p2pServer = nil + + pm.minter.stop() + + pm.stopped = true +} + +func (pm *ProtocolManager) NodeInfo() *RaftNodeInfo { + pm.mu.RLock() // as we read role and peers + defer pm.mu.RUnlock() + + roleDescription := "" + if pm.role == minterRole { + roleDescription = "minter" + } else if pm.isVerifierNode() { + roleDescription = "verifier" + } else if pm.isLearnerNode() { + roleDescription = "learner" + } + + peerAddresses := make([]*Address, len(pm.peers)) + peerIdx := 0 + for _, peer := range pm.peers { + peerAddresses[peerIdx] = peer.address + peerIdx += 1 + } + + removedPeerIfaces := pm.removedPeers + removedPeerIds := make([]uint16, removedPeerIfaces.Cardinality()) + i := 0 + for removedIface := range removedPeerIfaces.Iterator().C { + removedPeerIds[i] = removedIface.(uint16) + i++ + } + + // + // NOTE: before exposing any new fields here, make sure that the underlying + // ProtocolManager members are protected from concurrent access by pm.mu! + // + return &RaftNodeInfo{ + ClusterSize: len(pm.peers) + 1, + Role: roleDescription, + Address: pm.address, + PeerAddresses: peerAddresses, + RemovedPeerIds: removedPeerIds, + AppliedIndex: pm.appliedIndex, + SnapshotIndex: pm.snapshotIndex, + } +} + +// There seems to be a very rare race in raft where during `etcdRaft.StartNode` +// it will call back our `Process` method before it's finished returning the +// `raft.Node`, `pm.unsafeRawNode`, to us. This re-entrance through a separate +// thread will cause a nil pointer dereference. To work around this, this +// getter method should be used instead of reading `pm.unsafeRawNode` directly. +func (pm *ProtocolManager) rawNode() etcdRaft.Node { + for pm.unsafeRawNode == nil { + time.Sleep(100 * time.Millisecond) + } + + return pm.unsafeRawNode +} + +func (pm *ProtocolManager) nextRaftId() uint16 { + pm.mu.RLock() + defer pm.mu.RUnlock() + + maxId := pm.raftId + + for peerId := range pm.peers { + if maxId < peerId { + maxId = peerId + } + } + + removedPeerIfaces := pm.removedPeers + for removedIface := range removedPeerIfaces.Iterator().C { + removedId := removedIface.(uint16) + + if maxId < removedId { + maxId = removedId + } + } + + return maxId + 1 +} + +func (pm *ProtocolManager) isRaftIdRemoved(id uint16) bool { + pm.mu.RLock() + defer pm.mu.RUnlock() + + return pm.removedPeers.Contains(id) +} + +func (pm *ProtocolManager) isRaftIdUsed(raftId uint16) bool { + if pm.raftId == raftId || pm.isRaftIdRemoved(raftId) { + return true + } + + pm.mu.RLock() + defer pm.mu.RUnlock() + + return pm.peers[raftId] != nil +} + +func (pm *ProtocolManager) isNodeAlreadyInCluster(node *enode.Node) error { + pm.mu.RLock() + defer pm.mu.RUnlock() + + thisEnode := enode.MustParse(pm.p2pServer.NodeInfo().Enode) + if thisEnode.EnodeID() == node.EnodeID() { + return fmt.Errorf("enode is this enode (self): node with this enode has already been added to the cluster: %s", node.ID()) + } + + for _, peer := range pm.peers { + peerRaftId := peer.address.RaftId + peerNode := peer.p2pNode + + if peerNode.ID() == node.ID() { + return fmt.Errorf("node with this enode has already been added to the cluster: %s", node.ID()) + } + + if peerNode.IP().Equal(node.IP()) { + if peerNode.TCP() == node.TCP() { + return fmt.Errorf("existing node %v with raft ID %v is already using eth p2p at %v:%v", peerNode.ID(), peerRaftId, node.IP(), node.TCP()) + } else if peer.address.RaftPort == enr.RaftPort(node.RaftPort()) { + return fmt.Errorf("existing node %v with raft ID %v is already using raft at %v:%v", peerNode.ID(), peerRaftId, node.IP(), node.RaftPort()) + } + } + } + + return nil +} + +func (pm *ProtocolManager) peerExist(node *enode.Node) bool { + pm.mu.RLock() + defer pm.mu.RUnlock() + + for _, p := range pm.peers { + if node.ID() == p.p2pNode.ID() { + return true + } + } + return false +} + +func (pm *ProtocolManager) ProposeNewPeer(enodeURL string, isLearner bool) (uint16, error) { + if pm.isLearnerNode() { + return 0, errors.New("learner node can't add peer or learner") + } + node, err := enode.ParseV4(enodeURL) + if err != nil { + return 0, err + } + + if !pm.useDns { + // hostname is not allowed if DNS is not enabled + if node.Host() != "" { + return 0, fmt.Errorf("raft must enable dns to use hostname") + } + if len(node.IP()) != 4 { + return 0, fmt.Errorf("expected IPv4 address (with length 4), but got IP of length %v", len(node.IP())) + } + } + + if !node.HasRaftPort() { + return 0, fmt.Errorf("enodeId is missing raftport querystring parameter: %v", enodeURL) + } + + if err := pm.isNodeAlreadyInCluster(node); err != nil { + return 0, err + } + + raftId := pm.nextRaftId() + address := newAddress(raftId, node.RaftPort(), node, pm.useDns) + + confChangeType := raftpb.ConfChangeAddNode + + if isLearner { + confChangeType = raftpb.ConfChangeAddLearnerNode + } + + pm.confChangeProposalC <- raftpb.ConfChange{ + Type: confChangeType, + NodeID: uint64(raftId), + Context: address.toBytes(), + } + + return raftId, nil +} + +func (pm *ProtocolManager) ProposePeerRemoval(raftId uint16) error { + if pm.isLearnerNode() && raftId != pm.raftId { + return errors.New("learner node can't remove other peer") + } + pm.confChangeProposalC <- raftpb.ConfChange{ + Type: raftpb.ConfChangeRemoveNode, + NodeID: uint64(raftId), + } + return nil +} + +func (pm *ProtocolManager) PromoteToPeer(raftId uint16) (bool, error) { + if pm.isLearnerNode() { + return false, errors.New("learner node can't promote to peer") + } + + if !pm.isLearner(raftId) { + return false, fmt.Errorf("%d is not a learner. only learner can be promoted to peer", raftId) + } + + pm.confChangeProposalC <- raftpb.ConfChange{ + Type: raftpb.ConfChangeAddNode, + NodeID: uint64(raftId), + } + return true, nil +} + +// +// MsgWriter interface (necessary for p2p.Send) +// + +func (pm *ProtocolManager) WriteMsg(msg p2p.Msg) error { + // read *into* buffer + var buffer = make([]byte, msg.Size) + msg.Payload.Read(buffer) + + return pm.rawNode().Propose(context.TODO(), buffer) +} + +// +// Raft interface +// + +func (pm *ProtocolManager) Process(ctx context.Context, m raftpb.Message) error { + return pm.rawNode().Step(ctx, m) +} + +func (pm *ProtocolManager) IsIDRemoved(id uint64) bool { + return pm.isRaftIdRemoved(uint16(id)) +} + +func (pm *ProtocolManager) ReportUnreachable(id uint64) { + log.Info("peer is currently unreachable", "peer id", id) + + pm.rawNode().ReportUnreachable(id) +} + +func (pm *ProtocolManager) ReportSnapshot(id uint64, status etcdRaft.SnapshotStatus) { + if status == etcdRaft.SnapshotFailure { + log.Info("failed to send snapshot", "raft peer", id) + } else if status == etcdRaft.SnapshotFinish { + log.Info("finished sending snapshot", "raft peer", id) + } + + pm.rawNode().ReportSnapshot(id, status) +} + +// +// Private methods +// + +func (pm *ProtocolManager) startRaft() { + if !fileutil.Exist(pm.snapdir) { + if err := os.Mkdir(pm.snapdir, 0750); err != nil { + fatalf("cannot create dir for snapshot (%v)", err) + } + } + walExisted := wal.Exist(pm.waldir) + lastAppliedIndex := pm.loadAppliedIndex() + + id := raftTypes.ID(pm.raftId).String() + ss := stats.NewServerStats(id, id) + + pm.transport = &rafthttp.Transport{ + ID: raftTypes.ID(pm.raftId), + ClusterID: 0x1000, + Raft: pm, + ServerStats: ss, + LeaderStats: stats.NewLeaderStats(strconv.Itoa(int(pm.raftId))), + ErrorC: make(chan error), + } + pm.transport.Start() + + // We load the snapshot to connect to prev peers before replaying the WAL, + // which typically goes further into the future than the snapshot. + + var maybeRaftSnapshot *raftpb.Snapshot + + if walExisted { + maybeRaftSnapshot = pm.loadSnapshot() // re-establishes peer connections + } + + loadedWal, entries := pm.replayWAL(maybeRaftSnapshot) + pm.wal = loadedWal + + if walExisted { + + // If we shutdown but didn't manage to flush the state to disk, then it will be the case that we will only sync + // up to the snapshot. In this case, we can replay the raft entries that we have in saved to replay the blocks + // back into our chain. We output errors but cannot do much if one occurs, since we can't fork to a different + // chain and all other nodes in the network have confirmed these blocks + if maybeRaftSnapshot != nil { + currentChainHead := pm.blockchain.CurrentBlock().Number() + for _, entry := range entries { + if entry.Type == raftpb.EntryNormal { + var block types.Block + if err := rlp.DecodeBytes(entry.Data, &block); err != nil { + log.Error("error decoding block: ", "err", err) + continue + } + + if thisBlockHead := pm.blockchain.GetBlockByHash(block.Hash()); thisBlockHead != nil { + // check if the block is already existing in the local chain + // and the block number is greater than current chain head + if thisBlockHeadNum := thisBlockHead.Number(); thisBlockHeadNum.Cmp(currentChainHead) > 0 { + // insert the block only if its already seen + blocks := []*types.Block{&block} + if _, err := pm.blockchain.InsertChain(blocks); err != nil { + log.Error("error inserting the block into the chain", "number", block.NumberU64(), "hash", block.Hash(), "err", err) + } + } + } + } + } + } + + if hardState, _, err := pm.raftStorage.InitialState(); err != nil { + panic(fmt.Sprintf("failed to read initial state from raft while restarting: %v", err)) + } else { + if lastPersistedCommittedIndex := hardState.Commit; lastPersistedCommittedIndex < lastAppliedIndex { + log.Info("rolling back applied index to last-durably-committed", "last applied index", lastAppliedIndex, "last persisted index", lastPersistedCommittedIndex) + + // Roll back our applied index. See the logic and explanation around + // the single call to `pm.applyNewChainHead` for more context. + lastAppliedIndex = lastPersistedCommittedIndex + } + + // fix raft applied index out of range + firstIndex, err := pm.raftStorage.FirstIndex() + if err != nil { + panic(fmt.Sprintf("failed to read last persisted applied index from raft while restarting: %v", err)) + } + lastPersistedAppliedIndex := firstIndex - 1 + if lastPersistedAppliedIndex > lastAppliedIndex { + log.Debug("set lastAppliedIndex to lastPersistedAppliedIndex", "last applied index", lastAppliedIndex, "last persisted applied index", lastPersistedAppliedIndex) + + lastAppliedIndex = lastPersistedAppliedIndex + pm.advanceAppliedIndex(lastAppliedIndex) + } + } + } + + // NOTE: cockroach sets this to false for now until they've "worked out the + // bugs" + enablePreVote := true + + raftConfig := &etcdRaft.Config{ + Applied: lastAppliedIndex, + ID: uint64(pm.raftId), + ElectionTick: 10, // NOTE: cockroach sets this to 15 + HeartbeatTick: 1, // NOTE: cockroach sets this to 5 + Storage: pm.raftStorage, + + // NOTE, from cockroach: + // "PreVote and CheckQuorum are two ways of achieving the same thing. + // PreVote is more compatible with quiesced ranges, so we want to switch + // to it once we've worked out the bugs." + // + // TODO: vendor again? + // PreVote: enablePreVote, + CheckQuorum: !enablePreVote, + + // MaxSizePerMsg controls how many Raft log entries the leader will send to + // followers in a single MsgApp. + MaxSizePerMsg: 4096, // NOTE: in cockroachdb this is 16*1024 + + // MaxInflightMsgs controls how many in-flight messages Raft will send to + // a follower without hearing a response. The total number of Raft log + // entries is a combination of this setting and MaxSizePerMsg. + // + // NOTE: Cockroach's settings (MaxSizePerMsg of 4k and MaxInflightMsgs + // of 4) provide for up to 64 KB of raft log to be sent without + // acknowledgement. With an average entry size of 1 KB that translates + // to ~64 commands that might be executed in the handling of a single + // etcdraft.Ready operation. + MaxInflightMsgs: 256, // NOTE: in cockroachdb this is 4 + } + + log.Info("startRaft", "raft ID", raftConfig.ID) + + if walExisted { + log.Info("remounting an existing raft log; connecting to peers.") + + pm.unsafeRawNode = etcdRaft.RestartNode(raftConfig) + } else if pm.joinExisting { + log.Info("newly joining an existing cluster; waiting for connections.") + pm.unsafeRawNode = etcdRaft.StartNode(raftConfig, nil) + } else { + if numPeers := len(pm.bootstrapNodes); numPeers == 0 { + panic("exiting due to empty raft peers list") + } else { + log.Info("starting a new raft log", "initial cluster size of", numPeers) + } + + raftPeers, peerAddresses, localAddress := pm.makeInitialRaftPeers() + + pm.setLocalAddress(localAddress) + + // We add all peers up-front even though we will see a ConfChangeAddNode + // for each shortly. This is because raft's ConfState will contain all of + // these nodes before we see these log entries, and we always want our + // snapshots to have all addresses for each of the nodes in the ConfState. + for _, peerAddress := range peerAddresses { + pm.addPeer(peerAddress) + } + pm.unsafeRawNode = etcdRaft.StartNode(raftConfig, raftPeers) + } + log.Info("raft node started") + go pm.serveRaft() + go pm.serveLocalProposals() + go pm.eventLoop() + go pm.handleRoleChange(pm.rawNode().RoleChan().Out()) +} + +func (pm *ProtocolManager) setLocalAddress(addr *Address) { + pm.mu.Lock() + pm.address = addr + pm.mu.Unlock() + // By setting `URLs` on the raft transport, we advertise our URL (in an HTTP + // header) to any recipient. This is necessary for a newcomer to the cluster + // to be able to accept a snapshot from us to bootstrap them. + if urls, err := raftTypes.NewURLs([]string{pm.raftUrl(addr)}); err == nil { + pm.transport.URLs = urls + } else { + panic(fmt.Sprintf("error: could not create URL from local address: %v", addr)) + } +} + +func (pm *ProtocolManager) serveRaft() { + urlString := fmt.Sprintf("http://0.0.0.0:%d", pm.raftPort) + url, err := url.Parse(urlString) + if err != nil { + fatalf("Failed parsing URL (%v)", err) + } + + listener, err := newStoppableListener(url.Host, pm.httpstopc) + if err != nil { + fatalf("Failed to listen rafthttp (%v)", err) + } + err = (&http.Server{Handler: pm.transport.Handler()}).Serve(listener) + select { + case <-pm.httpstopc: + default: + fatalf("Failed to serve rafthttp (%v)", err) + } + close(pm.httpdonec) +} + +func (pm *ProtocolManager) isLearner(rid uint16) bool { + pm.mu.RLock() + defer pm.mu.RUnlock() + for _, n := range pm.confState.Learners { + if uint16(n) == rid { + return true + } + } + return false +} + +func (pm *ProtocolManager) isLearnerNode() bool { + return pm.isLearner(pm.raftId) +} + +func (pm *ProtocolManager) isVerifierNode() bool { + return pm.isVerifier(pm.raftId) +} + +func (pm *ProtocolManager) isVerifier(rid uint16) bool { + pm.mu.RLock() + defer pm.mu.RUnlock() + for _, n := range pm.confState.Nodes { + if uint16(n) == rid { + return true + } + } + return false +} + +func (pm *ProtocolManager) handleRoleChange(roleC <-chan interface{}) { + for { + select { + case role := <-roleC: + intRole, ok := role.(int) + + if !ok { + panic("Couldn't cast role to int") + } + if intRole == minterRole { + log.EmitCheckpoint(log.BecameMinter) + pm.minter.start() + } else { // verifier + if pm.isVerifierNode() { + log.EmitCheckpoint(log.BecameVerifier) + } else { + log.EmitCheckpoint(log.BecameLearner) + } + pm.minter.stop() + } + + pm.mu.Lock() + pm.role = intRole + pm.mu.Unlock() + case <-pm.quitSync: + return + } + } +} + +func (pm *ProtocolManager) minedBroadcastLoop() { + for obj := range pm.minedBlockSub.Chan() { + switch ev := obj.Data.(type) { + case core.NewMinedBlockEvent: + select { + case pm.blockProposalC <- ev.Block: + case <-pm.quitSync: + return + } + } + } +} + +// Serve two channels to handle new blocks and raft configuration changes originating locally. +func (pm *ProtocolManager) serveLocalProposals() { + // + // TODO: does it matter that this will restart from 0 whenever we restart a cluster? + // + var confChangeCount uint64 + + for { + select { + case block, ok := <-pm.blockProposalC: + if !ok { + log.Info("error: read from blockProposalC failed") + return + } + + size, r, err := rlp.EncodeToReader(block) + if err != nil { + panic(fmt.Sprintf("error: failed to send RLP-encoded block: %s", err.Error())) + } + var buffer = make([]byte, uint32(size)) + r.Read(buffer) + + // blocks until accepted by the raft state machine + pm.rawNode().Propose(context.TODO(), buffer) + case cc, ok := <-pm.confChangeProposalC: + if !ok { + log.Info("error: read from confChangeProposalC failed") + return + } + + confChangeCount++ + cc.ID = confChangeCount + pm.rawNode().ProposeConfChange(context.TODO(), cc) + case <-pm.quitSync: + return + } + } +} + +func (pm *ProtocolManager) entriesToApply(allEntries []raftpb.Entry) (entriesToApply []raftpb.Entry) { + if len(allEntries) == 0 { + return + } + + first := allEntries[0].Index + pm.mu.RLock() + lastApplied := pm.appliedIndex + pm.mu.RUnlock() + + if first > lastApplied+1 { + fatalf("first index of committed entry[%d] should <= appliedIndex[%d] + 1", first, lastApplied) + } + + firstToApply := lastApplied - first + 1 + + if firstToApply < uint64(len(allEntries)) { + entriesToApply = allEntries[firstToApply:] + } + return +} + +func (pm *ProtocolManager) raftUrl(address *Address) string { + if parsedIp := net.ParseIP(address.Hostname); parsedIp != nil { + if ipv4 := parsedIp.To4(); ipv4 != nil { + //this is an IPv4 address + return fmt.Sprintf("http://%s:%d", ipv4, address.RaftPort) + } + //this is an IPv6 address + return fmt.Sprintf("http://[%s]:%d", parsedIp, address.RaftPort) + } + return fmt.Sprintf("http://%s:%d", address.Hostname, address.RaftPort) +} + +func (pm *ProtocolManager) addPeer(address *Address) { + pm.mu.Lock() + defer pm.mu.Unlock() + + raftId := address.RaftId + + //Quorum - RAFT - derive pubkey from nodeId + pubKey, err := enode.HexPubkey(address.NodeId.String()) + if err != nil { + log.Error("error decoding pub key from enodeId", "enodeId", address.NodeId.String(), "err", err) + panic(err) + } + + // Add P2P connection: + p2pNode := enode.NewV4Hostname(pubKey, address.Hostname, int(address.P2pPort), 0, int(address.RaftPort)) + pm.p2pServer.AddPeer(p2pNode) + + // Add raft transport connection: + pm.transport.AddPeer(raftTypes.ID(raftId), []string{pm.raftUrl(address)}) + pm.peers[raftId] = &Peer{address, p2pNode} +} + +func (pm *ProtocolManager) disconnectFromPeer(raftId uint16, peer *Peer) { + pm.p2pServer.RemovePeer(peer.p2pNode) + pm.transport.RemovePeer(raftTypes.ID(raftId)) +} + +func (pm *ProtocolManager) removePeer(raftId uint16) { + pm.mu.Lock() + defer pm.mu.Unlock() + + if peer := pm.peers[raftId]; peer != nil { + pm.disconnectFromPeer(raftId, peer) + + delete(pm.peers, raftId) + } + + // This is only necessary sometimes, but it's idempotent. Also, we *always* + // do this, and not just when there's still a peer in the map, because we + // need to do it for our *own* raft ID before we get booted from the cluster + // so that snapshots are identical on all nodes. It's important for a booted + // node to have a snapshot identical to every other node because that node + // can potentially re-enter the cluster with a new raft ID. + pm.removedPeers.Add(raftId) +} + +func (pm *ProtocolManager) eventLoop() { + ticker := time.NewTicker(tickerMS * time.Millisecond) + defer ticker.Stop() + defer pm.wal.Close() + + exitAfterApplying := false + + for { + select { + case <-ticker.C: + pm.rawNode().Tick() + + // when the node is first ready it gives us entries to commit and messages + // to immediately publish + case rd := <-pm.rawNode().Ready(): + pm.wal.Save(rd.HardState, rd.Entries) + + if rd.SoftState != nil { + pm.updateLeader(rd.SoftState.Lead) + } + + if snap := rd.Snapshot; !etcdRaft.IsEmptySnap(snap) { + pm.saveRaftSnapshot(snap) + pm.applyRaftSnapshot(snap) + pm.advanceAppliedIndex(snap.Metadata.Index) + } + + // 1: Write HardState, Entries, and Snapshot to persistent storage if they + // are not empty. + pm.raftStorage.Append(rd.Entries) + + // 2: Send all Messages to the nodes named in the To field. + pm.transport.Send(rd.Messages) + + // 3: Apply Snapshot (if any) and CommittedEntries to the state machine. + for _, entry := range pm.entriesToApply(rd.CommittedEntries) { + switch entry.Type { + case raftpb.EntryNormal: + if len(entry.Data) == 0 { + break + } + var block types.Block + err := rlp.DecodeBytes(entry.Data, &block) + if err != nil { + log.Error("error decoding block", "err", err) + } + + if pm.blockchain.HasBlock(block.Hash(), block.NumberU64()) { + // This can happen: + // + // if (1) we crashed after applying this block to the chain, but + // before writing appliedIndex to LDB. + // or (2) we crashed in a scenario where we applied further than + // raft *durably persisted* its committed index (see + // https://github.com/coreos/etcd/pull/7899). In this + // scenario, when the node comes back up, we will re-apply + // a few entries. + + headBlockHash := pm.blockchain.CurrentBlock().Hash() + log.Warn("not applying already-applied block", "block hash", block.Hash(), "parent", block.ParentHash(), "head", headBlockHash) + } else { + if !pm.applyNewChainHead(&block) { + // return false only if insert chain is interrupted + // stop eventloop + return + } + } + + case raftpb.EntryConfChange: + var cc raftpb.ConfChange + cc.Unmarshal(entry.Data) + raftId := uint16(cc.NodeID) + + pm.confState = *pm.rawNode().ApplyConfChange(cc) + log.Info("confChange", "confState", pm.confState) + forceSnapshot := false + + switch cc.Type { + case raftpb.ConfChangeAddNode, raftpb.ConfChangeAddLearnerNode: + confChangeTypeName := raftpb.ConfChangeType_name[int32(cc.Type)] + log.Info(confChangeTypeName, "raft id", raftId) + if pm.isRaftIdRemoved(raftId) { + log.Info("ignoring "+confChangeTypeName+" for permanently-removed peer", "raft id", raftId) + } else if pm.isRaftIdUsed(raftId) && raftId <= uint16(len(pm.bootstrapNodes)) { + // See initial cluster logic in startRaft() for more information. + log.Info("ignoring expected "+confChangeTypeName+" for initial peer", "raft id", raftId) + // We need a snapshot to exist to reconnect to peers on start-up after a crash. + forceSnapshot = true + } else { // add peer or add learner or promote learner to voter + forceSnapshot = true + //if raft id exists as peer, you are promoting learner to peer + if pm.isRaftIdUsed(raftId) { + log.Info("promote learner node to voter node", "raft id", raftId) + } else { + //if raft id does not exist, you are adding peer/learner + log.Info("add peer/learner -> "+confChangeTypeName, "raft id", raftId) + pm.addPeer(bytesToAddress(cc.Context)) + } + } + + case raftpb.ConfChangeRemoveNode: + if pm.isRaftIdRemoved(raftId) { + log.Info("ignoring ConfChangeRemoveNode for already-removed peer", "raft id", raftId) + } else { + log.Info("removing peer due to ConfChangeRemoveNode", "raft id", raftId) + + forceSnapshot = true + + if raftId == pm.raftId { + exitAfterApplying = true + } + + pm.removePeer(raftId) + } + + case raftpb.ConfChangeUpdateNode: + // NOTE: remember to forceSnapshot in this case, if we add support + // for this. + fatalf("not yet handled: ConfChangeUpdateNode") + } + + if forceSnapshot { + // We force a snapshot here to persist our updated confState, so we + // know our fellow cluster members when we come back online. + // + // It is critical here to snapshot *before* writing our applied + // index in LevelDB, otherwise a crash while/before snapshotting + // (after advancing our applied index) would result in the loss of a + // cluster member upon restart: we would re-mount with an old + // ConfState. + pm.triggerSnapshot(entry.Index) + } + } + + pm.advanceAppliedIndex(entry.Index) + } + + pm.maybeTriggerSnapshot() + + if exitAfterApplying { + log.Warn("permanently removing self from the cluster") + pm.Stop() + log.Warn("permanently exited the cluster") + + return + } + + // 4: Call Node.Advance() to signal readiness for the next batch of + // updates. + pm.rawNode().Advance() + + case <-pm.quitSync: + return + } + } +} + +func (pm *ProtocolManager) makeInitialRaftPeers() (raftPeers []etcdRaft.Peer, peerAddresses []*Address, localAddress *Address) { + initialNodes := pm.bootstrapNodes + raftPeers = make([]etcdRaft.Peer, len(initialNodes)) // Entire cluster + peerAddresses = make([]*Address, len(initialNodes)-1) // Cluster without *this* node + + peersSeen := 0 + for i, node := range initialNodes { + raftId := uint16(i + 1) + // We initially get the raftPort from the enode ID's query string. As an alternative, we can move away from + // requiring the use of static peers for the initial set, and load them from e.g. another JSON file which + // contains pairs of enodes and raft ports, or we can get this initial peer list from commandline flags. + address := newAddress(raftId, node.RaftPort(), node, pm.useDns) + raftPeers[i] = etcdRaft.Peer{ + ID: uint64(raftId), + Context: address.toBytes(), + } + + if raftId == pm.raftId { + localAddress = address + } else { + peerAddresses[peersSeen] = address + peersSeen += 1 + } + } + + return +} + +func blockExtendsChain(block *types.Block, chain *core.BlockChain) bool { + return block.ParentHash() == chain.CurrentBlock().Hash() +} + +func (pm *ProtocolManager) applyNewChainHead(block *types.Block) bool { + if !blockExtendsChain(block, pm.blockchain) { + headBlock := pm.blockchain.CurrentBlock() + + log.Info("Non-extending block", "block", block.Hash(), "parent", block.ParentHash(), "head", headBlock.Hash()) + + pm.minter.invalidRaftOrderingChan <- InvalidRaftOrdering{headBlock: headBlock, invalidBlock: block} + } else { + if existingBlock := pm.blockchain.GetBlockByHash(block.Hash()); nil == existingBlock { + if err := pm.blockchain.Validator().ValidateBody(block); err != nil { + panic(fmt.Sprintf("failed to validate block %x (%v)", block.Hash(), err)) + } + } + + for _, tx := range block.Transactions() { + log.EmitCheckpoint(log.TxAccepted, "tx", tx.Hash().Hex()) + } + + _, err := pm.blockchain.InsertChain([]*types.Block{block}) + + if err != nil { + if err == core.ErrAbortBlocksProcessing { + log.Error(fmt.Sprintf("failed to extend chain: %s", err.Error())) + return false + } + panic(fmt.Sprintf("failed to extend chain: %s", err.Error())) + } + + log.EmitCheckpoint(log.BlockCreated, "block", fmt.Sprintf("%x", block.Hash())) + } + return true +} + +// Sets new appliedIndex in-memory, *and* writes this appliedIndex to LevelDB. +func (pm *ProtocolManager) advanceAppliedIndex(index uint64) { + pm.writeAppliedIndex(index) + + pm.mu.Lock() + pm.appliedIndex = index + pm.mu.Unlock() +} + +func (pm *ProtocolManager) updateLeader(leader uint64) { + pm.mu.Lock() + defer pm.mu.Unlock() + + pm.leader = uint16(leader) +} + +// The Address for the current leader, or an error if no leader is elected. +func (pm *ProtocolManager) LeaderAddress() (*Address, error) { + pm.mu.RLock() + defer pm.mu.RUnlock() + + if minterRole == pm.role { + return pm.address, nil + } else if l, ok := pm.peers[pm.leader]; ok { + return l.address, nil + } + // We expect to reach this if pm.leader is 0, which is how etcd denotes the lack of a leader. + return nil, errNoLeaderElected +} + +// Returns the raft id for a given enodeId +func (pm *ProtocolManager) FetchRaftId(enodeId string) (uint16, error) { + node, err := enode.ParseV4(enodeId) + if err != nil { + return 0, err + } + for raftId, peer := range pm.peers { + if peer.p2pNode.ID() == node.ID() { + return raftId, nil + } + } + return 0, fmt.Errorf("node not found in the cluster: %v", enodeId) +} diff --git a/raft/handler_test.go b/raft/handler_test.go new file mode 100644 index 0000000000..e55d0270fb --- /dev/null +++ b/raft/handler_test.go @@ -0,0 +1,196 @@ +package raft + +import ( + "crypto/ecdsa" + "encoding/binary" + "fmt" + "io/ioutil" + "net" + "os" + "reflect" + "testing" + "time" + "unsafe" + + "github.com/coreos/etcd/wal" + "github.com/coreos/etcd/wal/walpb" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" +) + +// pm.advanceAppliedIndex() and state updates are in different +// transaction boundaries hence there's a probablity that they are +// out of sync due to premature shutdown +func TestProtocolManager_whenAppliedIndexOutOfSync(t *testing.T) { + logger := log.New() + logger.SetHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) + tmpWorkingDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer func() { + _ = os.RemoveAll(tmpWorkingDir) + }() + count := 3 + ports := make([]uint16, count) + nodeKeys := make([]*ecdsa.PrivateKey, count) + peers := make([]*enode.Node, count) + for i := 0; i < count; i++ { + ports[i] = nextPort(t) + nodeKeys[i] = mustNewNodeKey(t) + peers[i] = enode.NewV4Hostname(&(nodeKeys[i].PublicKey), net.IPv4(127, 0, 0, 1).String(), 0, 0, int(ports[i])) + } + raftNodes := make([]*RaftService, count) + for i := 0; i < count; i++ { + if s, err := startRaftNode(uint16(i+1), ports[i], tmpWorkingDir, nodeKeys[i], peers); err != nil { + t.Fatal(err) + } else { + raftNodes[i] = s + } + } + waitFunc := func() { + for { + time.Sleep(10 * time.Millisecond) + for i := 0; i < count; i++ { + if raftNodes[i].raftProtocolManager.role == minterRole { + return + } + } + } + } + waitFunc() + logger.Debug("stop the cluster") + for i := 0; i < count; i++ { + if err := raftNodes[i].Stop(); err != nil { + t.Fatal(err) + } + // somehow the wal dir is still being locked that causes failures in subsequent start + // we need to check here to make sure everything is fully stopped + for isWalDirStillLocked(fmt.Sprintf("%s/node%d/raft-wal", tmpWorkingDir, i+1)) { + logger.Debug("sleep...", "i", i) + time.Sleep(10 * time.Millisecond) + } + logger.Debug("node stopped", "id", i) + } + logger.Debug("update applied index") + // update the index to mimic the issue (set applied index behind for node 0) + if err := writeAppliedIndex(tmpWorkingDir, 0, 1); err != nil { + t.Fatal(err) + } + //time.Sleep(3 * time.Second) + logger.Debug("restart the cluster") + for i := 0; i < count; i++ { + if s, err := startRaftNode(uint16(i+1), ports[i], tmpWorkingDir, nodeKeys[i], peers); err != nil { + t.Fatal(err) + } else { + raftNodes[i] = s + } + } + waitFunc() +} + +func isWalDirStillLocked(walDir string) bool { + var snap walpb.Snapshot + w, err := wal.Open(walDir, snap) + if err != nil { + return true + } + defer func() { + _ = w.Close() + }() + return false +} + +func writeAppliedIndex(workingDir string, node int, index uint64) error { + db, err := openQuorumRaftDb(fmt.Sprintf("%s/node%d/quorum-raft-state", workingDir, node+1)) + if err != nil { + return err + } + defer func() { + _ = db.Close() + }() + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, index) + return db.Put(appliedDbKey, buf, noFsync) +} + +func mustNewNodeKey(t *testing.T) *ecdsa.PrivateKey { + k, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + return k +} + +func nextPort(t *testing.T) uint16 { + listener, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatal(err) + } + return uint16(listener.Addr().(*net.TCPAddr).Port) +} + +func prepareServiceContext(key *ecdsa.PrivateKey) (ctx *node.ServiceContext, cfg *node.Config, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%s", r) + ctx = nil + cfg = nil + } + }() + cfg = &node.Config{ + P2P: p2p.Config{ + PrivateKey: key, + }, + } + ctx = &node.ServiceContext{ + EventMux: new(event.TypeMux), + } + // config is private field so we need some workaround to set the value + configField := reflect.ValueOf(ctx).Elem().FieldByName("Config") + configField = reflect.NewAt(configField.Type(), unsafe.Pointer(configField.UnsafeAddr())).Elem() + configField.Set(reflect.ValueOf(*cfg)) + return +} + +func startRaftNode(id, port uint16, tmpWorkingDir string, key *ecdsa.PrivateKey, nodes []*enode.Node) (*RaftService, error) { + datadir := fmt.Sprintf("%s/node%d", tmpWorkingDir, id) + + ctx, _, err := prepareServiceContext(key) + if err != nil { + return nil, err + } + + e, err := eth.New(ctx, ð.Config{ + Genesis: &core.Genesis{Config: params.QuorumTestChainConfig}, + }) + if err != nil { + return nil, err + } + + s, err := New(ctx, params.QuorumTestChainConfig, id, port, false, 100*time.Millisecond, e, nodes, datadir, false) + if err != nil { + return nil, err + } + + srv := &p2p.Server{ + Config: p2p.Config{ + PrivateKey: key, + }, + } + if err := srv.Start(); err != nil { + return nil, fmt.Errorf("could not start: %v", err) + } + if err := s.Start(srv); err != nil { + return nil, err + } + + return s, nil +} diff --git a/raft/listener.go b/raft/listener.go new file mode 100644 index 0000000000..4836b04d2e --- /dev/null +++ b/raft/listener.go @@ -0,0 +1,59 @@ +// Copyright 2015 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package raft + +import ( + "errors" + "net" + "time" +) + +// stoppableListener sets TCP keep-alive timeouts on accepted +// connections and waits on stopc message +type stoppableListener struct { + *net.TCPListener + stopc <-chan struct{} +} + +func newStoppableListener(addr string, stopc <-chan struct{}) (*stoppableListener, error) { + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + return &stoppableListener{ln.(*net.TCPListener), stopc}, nil +} + +func (ln stoppableListener) Accept() (c net.Conn, err error) { + connc := make(chan *net.TCPConn, 1) + errc := make(chan error, 1) + go func() { + tc, err := ln.AcceptTCP() + if err != nil { + errc <- err + return + } + connc <- tc + }() + select { + case <-ln.stopc: + return nil, errors.New("server stopped") + case err := <-errc: + return nil, err + case tc := <-connc: + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil + } +} diff --git a/raft/minter.go b/raft/minter.go new file mode 100644 index 0000000000..e77311bc0f --- /dev/null +++ b/raft/minter.go @@ -0,0 +1,446 @@ +// Copyright 2015 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 . + +package raft + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/eapache/channels" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for arbitrary signer vanity +) + +// Current state information for building the next block +type work struct { + config *params.ChainConfig + publicState *state.StateDB + privateState *state.StateDB + Block *types.Block + header *types.Header +} + +type minter struct { + config *params.ChainConfig + mu sync.Mutex + mux *event.TypeMux + eth *RaftService + chain *core.BlockChain + chainDb ethdb.Database + coinbase common.Address + minting int32 // Atomic status counter + shouldMine *channels.RingChannel + blockTime time.Duration + speculativeChain *speculativeChain + + invalidRaftOrderingChan chan InvalidRaftOrdering + chainHeadChan chan core.ChainHeadEvent + chainHeadSub event.Subscription + txPreChan chan core.NewTxsEvent + txPreSub event.Subscription +} + +type extraSeal struct { + RaftId []byte // RaftID of the block minter + Signature []byte // Signature of the block minter +} + +func newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Duration) *minter { + minter := &minter{ + config: config, + eth: eth, + mux: eth.EventMux(), + chainDb: eth.ChainDb(), + chain: eth.BlockChain(), + shouldMine: channels.NewRingChannel(1), + blockTime: blockTime, + speculativeChain: newSpeculativeChain(), + + invalidRaftOrderingChan: make(chan InvalidRaftOrdering, 1), + chainHeadChan: make(chan core.ChainHeadEvent, core.GetChainHeadChannleSize()), + txPreChan: make(chan core.NewTxsEvent, 4096), + } + + minter.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(minter.chainHeadChan) + minter.txPreSub = eth.TxPool().SubscribeNewTxsEvent(minter.txPreChan) + + minter.speculativeChain.clear(minter.chain.CurrentBlock()) + + go minter.eventLoop() + go minter.mintingLoop() + + return minter +} + +func (minter *minter) start() { + atomic.StoreInt32(&minter.minting, 1) + minter.requestMinting() +} + +func (minter *minter) stop() { + minter.mu.Lock() + defer minter.mu.Unlock() + + minter.speculativeChain.clear(minter.chain.CurrentBlock()) + atomic.StoreInt32(&minter.minting, 0) +} + +// Notify the minting loop that minting should occur, if it's not already been +// requested. Due to the use of a RingChannel, this function is idempotent if +// called multiple times before the minting occurs. +func (minter *minter) requestMinting() { + minter.shouldMine.In() <- struct{}{} +} + +type AddressTxes map[common.Address]types.Transactions + +func (minter *minter) updateSpeculativeChainPerNewHead(newHeadBlock *types.Block) { + minter.mu.Lock() + defer minter.mu.Unlock() + + minter.speculativeChain.accept(newHeadBlock) +} + +func (minter *minter) updateSpeculativeChainPerInvalidOrdering(headBlock *types.Block, invalidBlock *types.Block) { + invalidHash := invalidBlock.Hash() + + log.Info("Handling InvalidRaftOrdering", "invalid block", invalidHash, "current head", headBlock.Hash()) + + minter.mu.Lock() + defer minter.mu.Unlock() + + // 1. if the block is not in our db, exit. someone else mined this. + if !minter.chain.HasBlock(invalidHash, invalidBlock.NumberU64()) { + log.Info("Someone else mined invalid block; ignoring", "block", invalidHash) + + return + } + + minter.speculativeChain.unwindFrom(invalidHash, headBlock) +} + +func (minter *minter) eventLoop() { + defer minter.chainHeadSub.Unsubscribe() + defer minter.txPreSub.Unsubscribe() + + for { + select { + case ev := <-minter.chainHeadChan: + newHeadBlock := ev.Block + + if atomic.LoadInt32(&minter.minting) == 1 { + minter.updateSpeculativeChainPerNewHead(newHeadBlock) + + // + // TODO(bts): not sure if this is the place, but we're going to + // want to put an upper limit on our speculative mining chain + // length. + // + + minter.requestMinting() + } else { + minter.mu.Lock() + minter.speculativeChain.setHead(newHeadBlock) + minter.mu.Unlock() + } + + case <-minter.txPreChan: + if atomic.LoadInt32(&minter.minting) == 1 { + minter.requestMinting() + } + + case ev := <-minter.invalidRaftOrderingChan: + headBlock := ev.headBlock + invalidBlock := ev.invalidBlock + + minter.updateSpeculativeChainPerInvalidOrdering(headBlock, invalidBlock) + + // system stopped + case <-minter.chainHeadSub.Err(): + return + case <-minter.txPreSub.Err(): + return + } + } +} + +// Returns a wrapper around no-arg func `f` which can be called without limit +// and returns immediately: this will call the underlying func `f` at most once +// every `rate`. If this function is called more than once before the underlying +// `f` is invoked (per this rate limiting), `f` will only be called *once*. +// +// TODO(joel): this has a small bug in that you can't call it *immediately* when +// first allocated. +func throttle(rate time.Duration, f func()) func() { + request := channels.NewRingChannel(1) + + // every tick, block waiting for another request. then serve it immediately + go func() { + ticker := time.NewTicker(rate) + defer ticker.Stop() + + for range ticker.C { + <-request.Out() + f() + } + }() + + return func() { + request.In() <- struct{}{} + } +} + +// This function spins continuously, blocking until a block should be created +// (via requestMinting()). This is throttled by `minter.blockTime`: +// +// 1. A block is guaranteed to be minted within `blockTime` of being +// requested. +// 2. We never mint a block more frequently than `blockTime`. +func (minter *minter) mintingLoop() { + throttledMintNewBlock := throttle(minter.blockTime, func() { + if atomic.LoadInt32(&minter.minting) == 1 { + minter.mintNewBlock() + } + }) + + for range minter.shouldMine.Out() { + throttledMintNewBlock() + } +} + +func generateNanoTimestamp(parent *types.Block) (tstamp int64) { + parentTime := int64(parent.Time()) + tstamp = time.Now().UnixNano() + + if parentTime >= tstamp { + // Each successive block needs to be after its predecessor. + tstamp = parentTime + 1 + } + + return +} + +// Assumes mu is held. +func (minter *minter) createWork() *work { + parent := minter.speculativeChain.head + parentNumber := parent.Number() + tstamp := generateNanoTimestamp(parent) + + header := &types.Header{ + ParentHash: parent.Hash(), + Number: parentNumber.Add(parentNumber, common.Big1), + Difficulty: ethash.CalcDifficulty(minter.config, uint64(tstamp), parent.Header()), + GasLimit: minter.eth.calcGasLimitFunc(parent), + GasUsed: 0, + Coinbase: minter.coinbase, + Time: uint64(tstamp), + } + + publicState, privateState, err := minter.chain.StateAt(parent.Root()) + if err != nil { + panic(fmt.Sprint("failed to get parent state: ", err)) + } + + return &work{ + config: minter.config, + publicState: publicState, + privateState: privateState, + header: header, + } +} + +func (minter *minter) getTransactions() *types.TransactionsByPriceAndNonce { + allAddrTxes, err := minter.eth.TxPool().Pending() + if err != nil { // TODO: handle + panic(err) + } + addrTxes := minter.speculativeChain.withoutProposedTxes(allAddrTxes) + signer := types.MakeSigner(minter.chain.Config(), minter.chain.CurrentBlock().Number()) + return types.NewTransactionsByPriceAndNonce(signer, addrTxes) +} + +// Sends-off events asynchronously. +func (minter *minter) firePendingBlockEvents(logs []*types.Log) { + // Copy logs before we mutate them, adding a block hash. + copiedLogs := make([]*types.Log, len(logs)) + for i, l := range logs { + copiedLogs[i] = new(types.Log) + *copiedLogs[i] = *l + } + + go func() { + minter.mux.Post(core.PendingLogsEvent{Logs: copiedLogs}) + minter.mux.Post(core.PendingStateEvent{}) + }() +} + +func (minter *minter) mintNewBlock() { + minter.mu.Lock() + defer minter.mu.Unlock() + + work := minter.createWork() + transactions := minter.getTransactions() + + committedTxes, publicReceipts, _, logs := work.commitTransactions(transactions, minter.chain) + txCount := len(committedTxes) + + if txCount == 0 { + log.Info("Not minting a new block since there are no pending transactions") + return + } + + minter.firePendingBlockEvents(logs) + + header := work.header + + // commit state root after all state transitions. + ethash.AccumulateRewards(minter.chain.Config(), work.publicState, header, nil) + header.Root = work.publicState.IntermediateRoot(minter.chain.Config().IsEIP158(work.header.Number)) + + // update block hash since it is now available, but was not when the + // receipt/log of individual transactions were created: + headerHash := header.Hash() + for _, l := range logs { + l.BlockHash = headerHash + } + + //Sign the block and build the extraSeal struct + extraSealBytes := minter.buildExtraSeal(headerHash) + + // add vanity and seal to header + // NOTE: leaving vanity blank for now as a space for any future data + header.Extra = make([]byte, extraVanity+len(extraSealBytes)) + copy(header.Extra[extraVanity:], extraSealBytes) + + block := types.NewBlock(header, committedTxes, nil, publicReceipts) + + log.Info("Generated next block", "block num", block.Number(), "num txes", txCount) + + deleteEmptyObjects := minter.chain.Config().IsEIP158(block.Number()) + if err := minter.chain.CommitBlockWithState(deleteEmptyObjects, work.publicState, work.privateState); err != nil { + panic(err) + } + + minter.speculativeChain.extend(block) + + minter.mux.Post(core.NewMinedBlockEvent{Block: block}) + + elapsed := time.Since(time.Unix(0, int64(header.Time))) + log.Info("🔨 Mined block", "number", block.Number(), "hash", fmt.Sprintf("%x", block.Hash().Bytes()[:4]), "elapsed", elapsed) +} + +func (env *work) commitTransactions(txes *types.TransactionsByPriceAndNonce, bc *core.BlockChain) (types.Transactions, types.Receipts, types.Receipts, []*types.Log) { + var allLogs []*types.Log + var committedTxes types.Transactions + var publicReceipts types.Receipts + var privateReceipts types.Receipts + + gp := new(core.GasPool).AddGas(env.header.GasLimit) + txCount := 0 + + for { + tx := txes.Peek() + if tx == nil { + break + } + + env.publicState.Prepare(tx.Hash(), common.Hash{}, txCount) + + publicReceipt, privateReceipt, err := env.commitTransaction(tx, bc, gp) + switch { + case err != nil: + log.Info("TX failed, will be removed", "hash", tx.Hash(), "err", err) + txes.Pop() // skip rest of txes from this account + default: + txCount++ + committedTxes = append(committedTxes, tx) + + publicReceipts = append(publicReceipts, publicReceipt) + allLogs = append(allLogs, publicReceipt.Logs...) + + if privateReceipt != nil { + privateReceipts = append(privateReceipts, privateReceipt) + allLogs = append(allLogs, privateReceipt.Logs...) + } + + txes.Shift() + } + } + + return committedTxes, publicReceipts, privateReceipts, allLogs +} + +func (env *work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, gp *core.GasPool) (*types.Receipt, *types.Receipt, error) { + publicSnapshot := env.publicState.Snapshot() + privateSnapshot := env.privateState.Snapshot() + + var author *common.Address + var vmConf vm.Config + txnStart := time.Now() + publicReceipt, privateReceipt, err := core.ApplyTransaction(env.config, bc, author, gp, env.publicState, env.privateState, env.header, tx, &env.header.GasUsed, vmConf) + if err != nil { + env.publicState.RevertToSnapshot(publicSnapshot) + env.privateState.RevertToSnapshot(privateSnapshot) + + return nil, nil, err + } + log.EmitCheckpoint(log.TxCompleted, "tx", tx.Hash().Hex(), "time", time.Since(txnStart)) + + return publicReceipt, privateReceipt, nil +} + +func (minter *minter) buildExtraSeal(headerHash common.Hash) []byte { + //Sign the headerHash + nodeKey := minter.eth.nodeKey + sig, err := crypto.Sign(headerHash.Bytes(), nodeKey) + if err != nil { + log.Warn("Block sealing failed", "err", err) + } + + //build the extraSeal struct + raftIdString := hexutil.EncodeUint64(uint64(minter.eth.raftProtocolManager.raftId)) + + extra := extraSeal{ + RaftId: []byte(raftIdString[2:]), //remove the 0x prefix + Signature: sig, + } + + //encode to byte array for storage + extraDataBytes, err := rlp.EncodeToBytes(extra) + if err != nil { + log.Warn("Header.Extra Data Encoding failed", "err", err) + } + + return extraDataBytes +} diff --git a/raft/minter_test.go b/raft/minter_test.go new file mode 100644 index 0000000000..f544795dac --- /dev/null +++ b/raft/minter_test.go @@ -0,0 +1,208 @@ +package raft + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/coreos/etcd/raft/raftpb" + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +const TEST_URL = "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404" + +func TestSignHeader(t *testing.T) { + //create only what we need to test the seal + var testRaftId uint16 = 5 + config := &node.Config{Name: "unit-test", DataDir: ""} + nodeKey := config.NodeKey() + + raftProtocolManager := &ProtocolManager{raftId: testRaftId} + raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager} + minter := minter{eth: raftService} + + //create some fake header to sign + fakeParentHash := common.HexToHash("0xc2c1dc1be8054808c69e06137429899d") + + header := &types.Header{ + ParentHash: fakeParentHash, + Number: big.NewInt(1), + Difficulty: big.NewInt(1), + GasLimit: uint64(0), + GasUsed: uint64(0), + Coinbase: minter.coinbase, + Time: uint64(time.Now().UnixNano()), + } + + headerHash := header.Hash() + extraDataBytes := minter.buildExtraSeal(headerHash) + var seal *extraSeal + err := rlp.DecodeBytes(extraDataBytes[:], &seal) + if err != nil { + t.Fatalf("Unable to decode seal: %s", err.Error()) + } + + // Check raftId + sealRaftId, err := hexutil.DecodeUint64("0x" + string(seal.RaftId)) //add the 0x prefix + if err != nil { + t.Errorf("Unable to get RaftId: %s", err.Error()) + } + if sealRaftId != uint64(testRaftId) { + t.Errorf("RaftID does not match. Expected: %d, Actual: %d", testRaftId, sealRaftId) + } + + //Identify who signed it + sig := seal.Signature + pubKey, err := crypto.SigToPub(headerHash.Bytes(), sig) + if err != nil { + t.Fatalf("Unable to get public key from signature: %s", err.Error()) + } + + //Compare derived public key to original public key + if pubKey.X.Cmp(nodeKey.X) != 0 { + t.Errorf("Signature incorrect!") + } + +} + +func TestAddLearner_whenTypical(t *testing.T) { + + raftService := newTestRaftService(t, 1, []uint64{1}, []uint64{}) + + propPeer := func() { + raftid, err := raftService.raftProtocolManager.ProposeNewPeer(TEST_URL, true) + if err != nil { + t.Errorf("propose new peer failed %v\n", err) + } + if raftid != raftService.raftProtocolManager.raftId+1 { + t.Errorf("1. wrong raft id. expected %d got %d\n", raftService.raftProtocolManager.raftId+1, raftid) + } + } + go propPeer() + select { + case confChange := <-raftService.raftProtocolManager.confChangeProposalC: + if confChange.Type != raftpb.ConfChangeAddLearnerNode { + t.Errorf("expected ConfChangeAddLearnerNode but got %s", confChange.Type.String()) + } + if uint16(confChange.NodeID) != raftService.raftProtocolManager.raftId+1 { + t.Errorf("2. wrong raft id. expected %d got %d\n", raftService.raftProtocolManager.raftId+1, uint16(confChange.NodeID)) + } + case <-time.After(time.Millisecond * 200): + t.Errorf("add learner conf change not received") + } +} + +func TestPromoteLearnerToPeer_whenTypical(t *testing.T) { + learnerRaftId := uint16(3) + raftService := newTestRaftService(t, 2, []uint64{2}, []uint64{uint64(learnerRaftId)}) + promoteToPeer := func() { + ok, err := raftService.raftProtocolManager.PromoteToPeer(learnerRaftId) + if err != nil || !ok { + t.Errorf("promote learner to peer failed %v\n", err) + } + } + go promoteToPeer() + select { + case confChange := <-raftService.raftProtocolManager.confChangeProposalC: + if confChange.Type != raftpb.ConfChangeAddNode { + t.Errorf("expected ConfChangeAddNode but got %s", confChange.Type.String()) + } + if uint16(confChange.NodeID) != learnerRaftId { + t.Errorf("2. wrong raft id. expected %d got %d\n", learnerRaftId, uint16(confChange.NodeID)) + } + case <-time.After(time.Millisecond * 200): + t.Errorf("add learner conf change not received") + } +} + +func TestAddLearnerOrPeer_fromLearner(t *testing.T) { + + raftService := newTestRaftService(t, 3, []uint64{2}, []uint64{3}) + + _, err := raftService.raftProtocolManager.ProposeNewPeer(TEST_URL, true) + + if err == nil { + t.Errorf("learner should not be allowed to add learner or peer") + } + + if err != nil && !strings.Contains(err.Error(), "learner node can't add peer or learner") { + t.Errorf("expect error message: propose new peer failed, got: %v\n", err) + } + + _, err = raftService.raftProtocolManager.ProposeNewPeer(TEST_URL, false) + + if err == nil { + t.Errorf("learner should not be allowed to add learner or peer") + } + + if err != nil && !strings.Contains(err.Error(), "learner node can't add peer or learner") { + t.Errorf("expect error message: propose new peer failed, got: %v\n", err) + } + +} + +func TestPromoteLearnerToPeer_fromLearner(t *testing.T) { + learnerRaftId := uint16(3) + raftService := newTestRaftService(t, 2, []uint64{1}, []uint64{2, uint64(learnerRaftId)}) + + _, err := raftService.raftProtocolManager.PromoteToPeer(learnerRaftId) + + if err == nil { + t.Errorf("learner should not be allowed to promote to peer") + } + + if err != nil && !strings.Contains(err.Error(), "learner node can't promote to peer") { + t.Errorf("expect error message: propose new peer failed, got: %v\n", err) + } + +} + +func enodeId(id string, ip string, raftPort int) string { + return fmt.Sprintf("enode://%s@%s?discport=0&raftport=%d", id, ip, raftPort) +} + +func peerList(url string) (error, []*enode.Node) { + var nodes []*enode.Node + node, err := enode.ParseV4(url) + if err != nil { + return fmt.Errorf("Node URL %s: %v\n", url, err), nil + } + nodes = append(nodes, node) + return nil, nodes +} + +func newTestRaftService(t *testing.T, raftId uint16, nodes []uint64, learners []uint64) *RaftService { + //create only what we need to test add learner node + config := &node.Config{Name: "unit-test", DataDir: ""} + // This will create a new node key, which is needed to set a stub p2p.Server and avoid `nil pointer dereference` when testing. + nodeKey := config.NodeKey() + mockp2pConfig := p2p.Config{Name: "unit-test", ListenAddr: "30303", PrivateKey: nodeKey} + mockp2p := &p2p.Server{Config: mockp2pConfig} + + enodeIdStr := fmt.Sprintf("%x", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:]) + url := enodeId(enodeIdStr, "127.0.0.1:21001", 50401) + err, peers := peerList(url) + if err != nil { + t.Errorf("getting peers failed %v", err) + } + raftProtocolManager := &ProtocolManager{ + raftId: raftId, + bootstrapNodes: peers, + confChangeProposalC: make(chan raftpb.ConfChange), + removedPeers: mapset.NewSet(), + confState: raftpb.ConfState{Nodes: nodes, Learners: learners}, + p2pServer: mockp2p, + } + raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager} + return raftService +} diff --git a/raft/peer.go b/raft/peer.go new file mode 100644 index 0000000000..392d2c03eb --- /dev/null +++ b/raft/peer.go @@ -0,0 +1,116 @@ +package raft + +import ( + "bytes" + "fmt" + "log" + "net" + + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +// Serializable information about a Peer. Sufficient to build `etcdRaft.Peer` +// or `enode.Node`. +// As NodeId is mainly used to derive the `ecdsa.pubkey` to build `enode.Node` it is kept as [64]byte instead of ID [32]byte used by `enode.Node`. +type Address struct { + RaftId uint16 `json:"raftId"` + NodeId enode.EnodeID `json:"nodeId"` + Ip net.IP `json:"-"` + P2pPort enr.TCP `json:"p2pPort"` + RaftPort enr.RaftPort `json:"raftPort"` + + Hostname string `json:"hostname"` + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `json:"-" rlp:"tail"` +} + +type ClusterInfo struct { + Address + Role string `json:"role"` + NodeActive bool `json:"nodeActive"` +} + +func newAddress(raftId uint16, raftPort int, node *enode.Node, useDns bool) *Address { + // derive 64 byte nodeID from 128 byte enodeID + id, err := enode.RaftHexID(node.EnodeID()) + if err != nil { + panic(err) + } + if useDns && node.Host() != "" { + return &Address{ + RaftId: raftId, + NodeId: id, + Ip: nil, + P2pPort: enr.TCP(node.TCP()), + RaftPort: enr.RaftPort(raftPort), + Hostname: node.Host(), + } + } + return &Address{ + RaftId: raftId, + NodeId: id, + Ip: nil, + P2pPort: enr.TCP(node.TCP()), + RaftPort: enr.RaftPort(raftPort), + Hostname: node.IP().String(), + } +} + +// A peer that we're connected to via both raft's http transport, and ethereum p2p +type Peer struct { + address *Address // For raft transport + p2pNode *enode.Node // For ethereum transport +} + +// RLP Address encoding, for transport over raft and storage in LevelDB. +func (addr *Address) toBytes() []byte { + var toEncode interface{} + + // need to check if addr.Hostname is hostname/ip + if ip := net.ParseIP(addr.Hostname); ip == nil { + toEncode = addr + } else { + toEncode = []interface{}{addr.RaftId, addr.NodeId, ip, addr.P2pPort, addr.RaftPort} + } + + buffer, err := rlp.EncodeToBytes(toEncode) + if err != nil { + panic(fmt.Sprintf("error: failed to RLP-encode Address: %s", err.Error())) + } + return buffer +} + +func bytesToAddress(input []byte) *Address { + // try the new format first + addr := new(Address) + streamNew := rlp.NewStream(bytes.NewReader(input), 0) + if err := streamNew.Decode(addr); err == nil { + return addr + } + + // else try the old format + var temp struct { + RaftId uint16 + NodeId enode.EnodeID + Ip net.IP + P2pPort enr.TCP + RaftPort enr.RaftPort + } + + streamOld := rlp.NewStream(bytes.NewReader(input), 0) + if err := streamOld.Decode(&temp); err != nil { + log.Fatalf("failed to RLP-decode Address: %v", err) + } + + return &Address{ + RaftId: temp.RaftId, + NodeId: temp.NodeId, + Ip: nil, + P2pPort: temp.P2pPort, + RaftPort: temp.RaftPort, + Hostname: temp.Ip.String(), + } +} diff --git a/raft/persistence.go b/raft/persistence.go new file mode 100644 index 0000000000..a0c87170c1 --- /dev/null +++ b/raft/persistence.go @@ -0,0 +1,57 @@ +package raft + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/log" + + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +var ( + noFsync = &opt.WriteOptions{ + NoWriteMerge: false, + Sync: false, + } +) + +func openQuorumRaftDb(path string) (db *leveldb.DB, err error) { + // Open the db and recover any potential corruptions + db, err = leveldb.OpenFile(path, &opt.Options{ + OpenFilesCacheCapacity: -1, // -1 means 0?? + BlockCacheCapacity: -1, + }) + if _, corrupted := err.(*errors.ErrCorrupted); corrupted { + db, err = leveldb.RecoverFile(path, nil) + } + return +} + +func (pm *ProtocolManager) loadAppliedIndex() uint64 { + dat, err := pm.quorumRaftDb.Get(appliedDbKey, nil) + var lastAppliedIndex uint64 + if err == errors.ErrNotFound { + lastAppliedIndex = 0 + } else if err != nil { + fatalf("loadAppliedIndex error: %s", err) + } else { + lastAppliedIndex = binary.LittleEndian.Uint64(dat) + } + + pm.mu.Lock() + pm.appliedIndex = lastAppliedIndex + pm.mu.Unlock() + + log.Info("loaded the latest applied index", "lastAppliedIndex", lastAppliedIndex) + + return lastAppliedIndex +} + +func (pm *ProtocolManager) writeAppliedIndex(index uint64) { + log.Info("persisted the latest applied index", "index", index) + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, index) + pm.quorumRaftDb.Put(appliedDbKey, buf, noFsync) +} diff --git a/raft/snapshot.go b/raft/snapshot.go new file mode 100644 index 0000000000..8de57b41b0 --- /dev/null +++ b/raft/snapshot.go @@ -0,0 +1,393 @@ +package raft + +import ( + "bytes" + "fmt" + "io" + "math/big" + "net" + "sort" + "time" + + "github.com/coreos/etcd/raft/raftpb" + "github.com/coreos/etcd/snap" + "github.com/coreos/etcd/wal/walpb" + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/permission/core" + "github.com/ethereum/go-ethereum/rlp" +) + +type SnapshotWithHostnames struct { + Addresses []Address + RemovedRaftIds []uint16 + HeadBlockHash common.Hash +} + +type AddressWithoutHostname struct { + RaftId uint16 + NodeId enode.EnodeID + Ip net.IP + P2pPort enr.TCP + RaftPort enr.RaftPort +} + +type SnapshotWithoutHostnames struct { + Addresses []AddressWithoutHostname + RemovedRaftIds []uint16 // Raft IDs for permanently removed peers + HeadBlockHash common.Hash +} + +type ByRaftId []Address + +func (a ByRaftId) Len() int { return len(a) } +func (a ByRaftId) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByRaftId) Less(i, j int) bool { return a[i].RaftId < a[j].RaftId } + +func (pm *ProtocolManager) buildSnapshot() *SnapshotWithHostnames { + pm.mu.RLock() + defer pm.mu.RUnlock() + + numNodes := len(pm.confState.Nodes) + len(pm.confState.Learners) + numRemovedNodes := pm.removedPeers.Cardinality() + + snapshot := &SnapshotWithHostnames{ + Addresses: make([]Address, numNodes), + RemovedRaftIds: make([]uint16, numRemovedNodes), + HeadBlockHash: pm.blockchain.CurrentBlock().Hash(), + } + + // Populate addresses + + for i, rawRaftId := range append(pm.confState.Nodes, pm.confState.Learners...) { + raftId := uint16(rawRaftId) + + if raftId == pm.raftId { + snapshot.Addresses[i] = *pm.address + } else { + snapshot.Addresses[i] = *pm.peers[raftId].address + } + } + sort.Sort(ByRaftId(snapshot.Addresses)) + + // Populate removed IDs + i := 0 + for removedIface := range pm.removedPeers.Iterator().C { + snapshot.RemovedRaftIds[i] = removedIface.(uint16) + i++ + } + return snapshot +} + +// Note that we do *not* read `pm.appliedIndex` here. We only use the `index` +// parameter instead. This is because we need to support a scenario when we +// snapshot for a future index that we have not yet recorded in LevelDB. See +// comments around the use of `forceSnapshot`. +func (pm *ProtocolManager) triggerSnapshot(index uint64) { + pm.mu.RLock() + snapshotIndex := pm.snapshotIndex + pm.mu.RUnlock() + + log.Info("start snapshot", "applied index", pm.appliedIndex, "last snapshot index", snapshotIndex) + + //snapData := pm.blockchain.CurrentBlock().Hash().Bytes() + //snap, err := pm.raftStorage.CreateSnapshot(pm.appliedIndex, &pm.confState, snapData) + snapData := pm.buildSnapshot().toBytes() + snap, err := pm.raftStorage.CreateSnapshot(index, &pm.confState, snapData) + if err != nil { + panic(err) + } + if err := pm.saveRaftSnapshot(snap); err != nil { + panic(err) + } + // Discard all log entries prior to index. + if err := pm.raftStorage.Compact(index); err != nil { + panic(err) + } + log.Info("compacted log", "index", pm.appliedIndex) + + pm.mu.Lock() + pm.snapshotIndex = index + pm.mu.Unlock() +} + +func confStateIdSet(confState raftpb.ConfState) mapset.Set { + set := mapset.NewSet() + for _, rawRaftId := range append(confState.Nodes, confState.Learners...) { + set.Add(uint16(rawRaftId)) + } + return set +} + +func (pm *ProtocolManager) updateClusterMembership(newConfState raftpb.ConfState, addresses []Address, removedRaftIds []uint16) { + log.Info("updating cluster membership per raft snapshot") + + prevConfState := pm.confState + + // Update tombstones for permanently removed peers. For simplicity we do not + // allow the re-use of peer IDs once a peer is removed. + + removedPeers := mapset.NewSet() + for _, removedRaftId := range removedRaftIds { + removedPeers.Add(removedRaftId) + } + pm.mu.Lock() + pm.removedPeers = removedPeers + pm.mu.Unlock() + + // Remove old peers that we're still connected to + + prevIds := confStateIdSet(prevConfState) + newIds := confStateIdSet(newConfState) + idsToRemove := prevIds.Difference(newIds) + for idIfaceToRemove := range idsToRemove.Iterator().C { + raftId := idIfaceToRemove.(uint16) + log.Info("removing old raft peer", "peer id", raftId) + + pm.removePeer(raftId) + } + + // Update local and remote addresses + + for _, tempAddress := range addresses { + address := tempAddress // Allocate separately on the heap for each iteration. + + if address.RaftId == pm.raftId { + // If we're a newcomer to an existing cluster, this is where we learn + // our own Address. + pm.setLocalAddress(&address) + } else { + pm.mu.RLock() + existingPeer := pm.peers[address.RaftId] + pm.mu.RUnlock() + + if existingPeer == nil { + log.Info("adding new raft peer", "raft id", address.RaftId) + pm.addPeer(&address) + } + } + } + + pm.mu.Lock() + pm.confState = newConfState + pm.mu.Unlock() + + log.Info("updated cluster membership") +} + +func (pm *ProtocolManager) maybeTriggerSnapshot() { + pm.mu.RLock() + appliedIndex := pm.appliedIndex + entriesSinceLastSnap := appliedIndex - pm.snapshotIndex + pm.mu.RUnlock() + + if entriesSinceLastSnap < snapshotPeriod { + return + } + + pm.triggerSnapshot(appliedIndex) +} + +func (pm *ProtocolManager) loadSnapshot() *raftpb.Snapshot { + if raftSnapshot := pm.readRaftSnapshot(); raftSnapshot != nil { + log.Info("loading snapshot") + pm.applyRaftSnapshot(*raftSnapshot) + + return raftSnapshot + } else { + log.Info("no snapshot to load") + + return nil + } +} + +func (snapshot *SnapshotWithHostnames) toBytes() []byte { + var ( + useOldSnapshot bool + oldSnapshot SnapshotWithoutHostnames + toEncode interface{} + ) + + // use old snapshot if all snapshot.Addresses are ips + // but use the new snapshot if any of it is a hostname + useOldSnapshot = true + oldSnapshot.HeadBlockHash, oldSnapshot.RemovedRaftIds = snapshot.HeadBlockHash, snapshot.RemovedRaftIds + oldSnapshot.Addresses = make([]AddressWithoutHostname, len(snapshot.Addresses)) + + for index, addrWithHost := range snapshot.Addresses { + // validate addrWithHost.Hostname is a hostname/ip + ip := net.ParseIP(addrWithHost.Hostname) + if ip == nil { + // this is a hostname + useOldSnapshot = false + break + } + // this is an ip + oldSnapshot.Addresses[index] = AddressWithoutHostname{ + addrWithHost.RaftId, + addrWithHost.NodeId, + ip, + addrWithHost.P2pPort, + addrWithHost.RaftPort, + } + } + + if useOldSnapshot { + toEncode = oldSnapshot + } else { + toEncode = snapshot + } + buffer, err := rlp.EncodeToBytes(toEncode) + if err != nil { + panic(fmt.Sprintf("error: failed to RLP-encode Snapshot: %s", err.Error())) + } + return buffer +} + +func bytesToSnapshot(input []byte) *SnapshotWithHostnames { + var err, errOld error + + snapshot := new(SnapshotWithHostnames) + streamNewSnapshot := rlp.NewStream(bytes.NewReader(input), 0) + if err = streamNewSnapshot.Decode(snapshot); err == nil { + return snapshot + } + + // Build new snapshot with hostname from legacy Address struct + snapshotOld := new(SnapshotWithoutHostnames) + streamOldSnapshot := rlp.NewStream(bytes.NewReader(input), 0) + if errOld = streamOldSnapshot.Decode(snapshotOld); errOld == nil { + var snapshotConverted SnapshotWithHostnames + snapshotConverted.RemovedRaftIds, snapshotConverted.HeadBlockHash = snapshotOld.RemovedRaftIds, snapshotOld.HeadBlockHash + snapshotConverted.Addresses = make([]Address, len(snapshotOld.Addresses)) + + for index, oldAddrWithIp := range snapshotOld.Addresses { + snapshotConverted.Addresses[index] = Address{ + RaftId: oldAddrWithIp.RaftId, + NodeId: oldAddrWithIp.NodeId, + Ip: nil, + P2pPort: oldAddrWithIp.P2pPort, + RaftPort: oldAddrWithIp.RaftPort, + Hostname: oldAddrWithIp.Ip.String(), + } + } + + return &snapshotConverted + } + + fatalf("failed to RLP-decode Snapshot: %v, %v", err, errOld) + return nil +} + +func (snapshot *SnapshotWithHostnames) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{snapshot.Addresses, snapshot.RemovedRaftIds, snapshot.HeadBlockHash}) +} + +// Raft snapshot + +func (pm *ProtocolManager) saveRaftSnapshot(snap raftpb.Snapshot) error { + if err := pm.snapshotter.SaveSnap(snap); err != nil { + return err + } + + walSnap := walpb.Snapshot{ + Index: snap.Metadata.Index, + Term: snap.Metadata.Term, + } + + if err := pm.wal.SaveSnapshot(walSnap); err != nil { + return err + } + + return pm.wal.ReleaseLockTo(snap.Metadata.Index) +} + +func (pm *ProtocolManager) readRaftSnapshot() *raftpb.Snapshot { + snapshot, err := pm.snapshotter.Load() + if err != nil && err != snap.ErrNoSnapshot { + fatalf("error loading snapshot: %v", err) + } + + return snapshot +} + +func (pm *ProtocolManager) applyRaftSnapshot(raftSnapshot raftpb.Snapshot) { + log.Info("applying snapshot to raft storage") + if err := pm.raftStorage.ApplySnapshot(raftSnapshot); err != nil { + fatalf("failed to apply snapshot: %s", err) + } + snapshot := bytesToSnapshot(raftSnapshot.Data) + + latestBlockHash := snapshot.HeadBlockHash + + pm.updateClusterMembership(raftSnapshot.Metadata.ConfState, snapshot.Addresses, snapshot.RemovedRaftIds) + + preSyncHead := pm.blockchain.CurrentBlock() + + if latestBlock := pm.blockchain.GetBlockByHash(latestBlockHash); latestBlock == nil { + pm.syncBlockchainUntil(latestBlockHash) + pm.logNewlyAcceptedTransactions(preSyncHead) + + log.Info(chainExtensionMessage, "hash", pm.blockchain.CurrentBlock().Hash()) + } else { + // added for permissions changes to indicate node sync up has started + core.SetSyncStatus() + log.Info("blockchain is caught up; no need to synchronize") + } + + snapMeta := raftSnapshot.Metadata + pm.confState = snapMeta.ConfState + pm.mu.Lock() + pm.snapshotIndex = snapMeta.Index + pm.mu.Unlock() +} + +func (pm *ProtocolManager) syncBlockchainUntil(hash common.Hash) { + pm.mu.RLock() + peerMap := make(map[uint16]*Peer, len(pm.peers)) + for raftId, peer := range pm.peers { + peerMap[raftId] = peer + } + pm.mu.RUnlock() + + for { + for peerId, peer := range peerMap { + log.Info("synchronizing with peer", "peer id", peerId, "hash", hash) + + peerId := peer.p2pNode.ID().String() + peerIdPrefix := fmt.Sprintf("%x", peer.p2pNode.ID().Bytes()[:8]) + + if err := pm.downloader.Synchronise(peerIdPrefix, hash, big.NewInt(0), downloader.BoundedFullSync); err != nil { + log.Info("failed to synchronize with peer", "peer id", peerId) + + time.Sleep(500 * time.Millisecond) + } else { + return + } + } + } +} + +func (pm *ProtocolManager) logNewlyAcceptedTransactions(preSyncHead *types.Block) { + newHead := pm.blockchain.CurrentBlock() + numBlocks := newHead.NumberU64() - preSyncHead.NumberU64() + blocks := make([]*types.Block, numBlocks) + currBlock := newHead + blocksSeen := 0 + for currBlock.Hash() != preSyncHead.Hash() { + blocks[int(numBlocks)-(1+blocksSeen)] = currBlock + + blocksSeen += 1 + currBlock = pm.blockchain.GetBlockByHash(currBlock.ParentHash()) + } + for _, block := range blocks { + for _, tx := range block.Transactions() { + log.EmitCheckpoint(log.TxAccepted, "tx", tx.Hash().Hex()) + } + } +} diff --git a/raft/speculative_chain.go b/raft/speculative_chain.go new file mode 100644 index 0000000000..3fa8806cc8 --- /dev/null +++ b/raft/speculative_chain.go @@ -0,0 +1,187 @@ +package raft + +import ( + mapset "github.com/deckarep/golang-set" + "gopkg.in/oleiade/lane.v1" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// The speculative chain represents blocks that we have minted which haven't been accepted into the chain yet, building +// on each other in a chain. It has three basic operations: +// * add new block to end +// * accept / remove oldest block +// * unwind / remove invalid blocks to the end +// +// Additionally: +// * clear state when we stop minting +// * set the parent when we're not minting (so it's always current) +type speculativeChain struct { + head *types.Block + unappliedBlocks *lane.Deque + expectedInvalidBlockHashes mapset.Set // This is thread-safe. This set is referred to as our "guard" below. + proposedTxes mapset.Set // This is thread-safe. +} + +func newSpeculativeChain() *speculativeChain { + return &speculativeChain{ + head: nil, + unappliedBlocks: lane.NewDeque(), + expectedInvalidBlockHashes: mapset.NewSet(), + proposedTxes: mapset.NewSet(), + } +} + +func (chain *speculativeChain) clear(block *types.Block) { + chain.head = block + chain.unappliedBlocks = lane.NewDeque() + chain.expectedInvalidBlockHashes.Clear() + chain.proposedTxes.Clear() +} + +// Append a new speculative block +func (chain *speculativeChain) extend(block *types.Block) { + chain.head = block + chain.recordProposedTransactions(block.Transactions()) + chain.unappliedBlocks.Append(block) +} + +// Set the parent of the speculative chain +// +// Note: This is only called when not minter +func (chain *speculativeChain) setHead(block *types.Block) { + chain.head = block +} + +// Accept this block, removing it from the speculative chain +func (chain *speculativeChain) accept(acceptedBlock *types.Block) { + earliestProposedI := chain.unappliedBlocks.Shift() + var earliestProposed *types.Block + if nil != earliestProposedI { + earliestProposed = earliestProposedI.(*types.Block) + } + + // There are three possible scenarios: + // 1. We don't have a record of this block (or any proposed blocks), meaning someone else minted it and we should + // add it as the new head of our speculative chain. New blocks from the old leader are still coming in. + // 2. This block was the first outstanding one we proposed. + // 3. This block is different from the block we proposed, (also) meaning new blocks are still coming in from the old + // leader, but unlike the first scenario, we need to clear all of the speculative chain state because the + // `acceptedBlock` takes precedence over our speculative state. + if earliestProposed == nil { + chain.head = acceptedBlock + } else if expectedBlock := earliestProposed.Hash() == acceptedBlock.Hash(); expectedBlock { + // Remove the txes in this accepted block from our blacklist. + chain.removeProposedTxes(acceptedBlock) + } else { + log.Info("Another node minted; Clearing speculative state", "block", acceptedBlock.Hash()) + + chain.clear(acceptedBlock) + } +} + +// Remove all blocks in the chain from the specified one until the end +func (chain *speculativeChain) unwindFrom(invalidHash common.Hash, headBlock *types.Block) { + + // check our "guard" to see if this is a (descendant) block we're + // expected to be ruled invalid. if we find it, remove from the guard + if chain.expectedInvalidBlockHashes.Contains(invalidHash) { + log.Info("Removing expected-invalid block from guard.", "block", invalidHash) + + chain.expectedInvalidBlockHashes.Remove(invalidHash) + + return + } + + // pop from the RHS repeatedly, updating minter.parent each time. if not + // our block, add to guard. in all cases, call removeProposedTxes + for { + currBlockI := chain.unappliedBlocks.Pop() + + if nil == currBlockI { + log.Info("(Popped all blocks from queue.)") + + break + } + + currBlock := currBlockI.(*types.Block) + + log.Info("Popped block from queue RHS.", "block", currBlock.Hash()) + + // Maintain invariant: the parent always points the last speculative block or the head of the blockchain + // if there are not speculative blocks. + if speculativeParentI := chain.unappliedBlocks.Last(); nil != speculativeParentI { + chain.head = speculativeParentI.(*types.Block) + } else { + chain.head = headBlock + } + + chain.removeProposedTxes(currBlock) + + if currBlock.Hash() != invalidHash { + log.Info("Haven't yet found block; adding descendent to guard.\n", "invalid block", invalidHash, "descendant", currBlock.Hash()) + + chain.expectedInvalidBlockHashes.Add(currBlock.Hash()) + } else { + break + } + } +} + +// We keep track of txes we've put in all newly-mined blocks since the last +// ChainHeadEvent, and filter them out so that we don't try to create blocks +// with the same transactions. This is necessary because the TX pool will keep +// supplying us these transactions until they are in the chain (after having +// flown through raft). +func (chain *speculativeChain) recordProposedTransactions(txes types.Transactions) { + txHashIs := make([]interface{}, len(txes)) + for i, tx := range txes { + txHashIs[i] = tx.Hash() + } + for _, i := range txHashIs { + chain.proposedTxes.Add(i) + } +} + +// Removes txes in block from our "blacklist" of "proposed tx" hashes. When we +// create a new block and use txes from the tx pool, we ignore those that we +// have already used ("proposed"), but that haven't yet officially made it into +// the chain yet. +// +// It's important to remove hashes from this blacklist (once we know we don't +// need them in there anymore) so that it doesn't grow endlessly. +func (chain *speculativeChain) removeProposedTxes(block *types.Block) { + minedTxes := block.Transactions() + minedTxInterfaces := make([]interface{}, len(minedTxes)) + for i, tx := range minedTxes { + minedTxInterfaces[i] = tx.Hash() + } + + // NOTE: we are using a thread-safe Set here, so it's fine if we access this + // here and in mintNewBlock concurrently. using a finer-grained set-specific + // lock here is preferable, because mintNewBlock holds its locks for a + // nontrivial amount of time. + for _, i := range minedTxInterfaces { + chain.proposedTxes.Remove(i) + } +} + +func (chain *speculativeChain) withoutProposedTxes(addrTxes AddressTxes) AddressTxes { + newMap := make(AddressTxes) + + for addr, txes := range addrTxes { + filteredTxes := make(types.Transactions, 0) + for _, tx := range txes { + if !chain.proposedTxes.Contains(tx.Hash()) { + filteredTxes = append(filteredTxes, tx) + } + } + if len(filteredTxes) > 0 { + newMap[addr] = filteredTxes + } + } + + return newMap +} diff --git a/raft/util.go b/raft/util.go new file mode 100644 index 0000000000..74e7015cf3 --- /dev/null +++ b/raft/util.go @@ -0,0 +1,30 @@ +package raft + +import ( + "fmt" + "io" + "os" + "runtime" +) + +// TODO: this is just copied over from cmd/utils/cmd.go. dedupe + +// Fatalf formats a message to standard error and exits the program. +// The message is also printed to standard output if standard error +// is redirected to a different file. +func fatalf(format string, args ...interface{}) { + w := io.MultiWriter(os.Stdout, os.Stderr) + if runtime.GOOS == "windows" { + // The SameFile check below doesn't work on Windows. + // stdout is unlikely to get redirected though, so just print there. + w = os.Stdout + } else { + outf, _ := os.Stdout.Stat() + errf, _ := os.Stderr.Stat() + if outf != nil && errf != nil && os.SameFile(outf, errf) { + w = os.Stderr + } + } + fmt.Fprintf(w, "Fatal: "+format+"\n", args...) + os.Exit(1) +} diff --git a/raft/wal.go b/raft/wal.go new file mode 100644 index 0000000000..ca04d99472 --- /dev/null +++ b/raft/wal.go @@ -0,0 +1,54 @@ +package raft + +import ( + "os" + + "github.com/coreos/etcd/raft/raftpb" + "github.com/coreos/etcd/wal" + "github.com/coreos/etcd/wal/walpb" + "github.com/ethereum/go-ethereum/log" +) + +func (pm *ProtocolManager) openWAL(maybeRaftSnapshot *raftpb.Snapshot) *wal.WAL { + if !wal.Exist(pm.waldir) { + if err := os.Mkdir(pm.waldir, 0750); err != nil { + fatalf("cannot create waldir: %s", err) + } + + wal, err := wal.Create(pm.waldir, nil) + if err != nil { + fatalf("failed to create waldir: %s", err) + } + wal.Close() + } + + walsnap := walpb.Snapshot{} + + log.Info("loading WAL", "term", walsnap.Term, "index", walsnap.Index) + + if maybeRaftSnapshot != nil { + walsnap.Index, walsnap.Term = maybeRaftSnapshot.Metadata.Index, maybeRaftSnapshot.Metadata.Term + } + + wal, err := wal.Open(pm.waldir, walsnap) + if err != nil { + fatalf("error loading WAL: %s", err) + } + + return wal +} + +func (pm *ProtocolManager) replayWAL(maybeRaftSnapshot *raftpb.Snapshot) (*wal.WAL, []raftpb.Entry) { + log.Info("replaying WAL") + wal := pm.openWAL(maybeRaftSnapshot) + + _, hardState, entries, err := wal.ReadAll() + if err != nil { + fatalf("failed to read WAL: %s", err) + } + + pm.raftStorage.SetHardState(hardState) + pm.raftStorage.Append(entries) + + return wal, entries +} diff --git a/rpc/client.go b/rpc/client.go index 984f56bc69..f467ccd197 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -561,6 +561,7 @@ func (c *Client) dispatch(codec ServerCodec) { conn.handler.log.Debug("RPC connection read error", "err", err) conn.close(err, lastOp) reading = false + c.writeConn = nil // Reconnect: case newcodec := <-c.reconnected: @@ -629,3 +630,18 @@ func (c *Client) read(codec ServerCodec) { c.readOp <- readOp{msgs, batch} } } + +// Quorum +// +// Secure HTTP requests with authorization header +// Do nothing if transport is not HTTP +func (c *Client) WithHTTPCredentials(providerFunc HttpCredentialsProviderFunc) (*Client, error) { + if !c.isHTTP { + return c, nil + } + // usually c.isHTTP check is sufficient, the below enforces the defensive check + if conn, ok := c.writeConn.(*httpConn); ok { + conn.credentialsProvider = providerFunc + } + return c, nil +} diff --git a/rpc/client_test.go b/rpc/client_test.go index 19c2facb55..f78b1b14f2 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -32,6 +32,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/assert" ) func TestClientRequest(t *testing.T) { @@ -612,3 +613,55 @@ func (l *flakeyListener) Accept() (net.Conn, error) { } return c, err } + +func TestClient_withCredentials_whenTargetingHTTP(t *testing.T) { + server := newTestServer() + server.authenticationManager = &stubAuthenticationManager{isEnabled: true} + defer server.Stop() + fl := &flakeyListener{ + maxAcceptDelay: 1 * time.Second, + maxKillTimeout: 600 * time.Millisecond, + } + hs := httptest.NewUnstartedServer(server) + fl.Listener = hs.Listener + hs.Listener = fl + // Connect the client. + hs.Start() + defer hs.Close() + + c, err := Dial("http://" + hs.Listener.Addr().String()) + assert.NoError(t, err) + var f HttpCredentialsProviderFunc = func(ctx context.Context) (string, error) { + return "Bearer arbitrary_token", nil + } + authenticatedClient, err := c.WithHTTPCredentials(f) + assert.NoError(t, err) + + err = authenticatedClient.CallContext(context.Background(), nil, "arbitrary_call") + assert.EqualError(t, err, "arbitrary_call - access denied") +} + +func TestClient_withCredentials_whenTargetingWS(t *testing.T) { + server := newTestServer() + server.authenticationManager = &stubAuthenticationManager{isEnabled: true} + defer server.Stop() + fl := &flakeyListener{ + maxAcceptDelay: 1 * time.Second, + maxKillTimeout: 600 * time.Millisecond, + } + hs := httptest.NewUnstartedServer(server.WebsocketHandler([]string{"*"})) + fl.Listener = hs.Listener + hs.Listener = fl + // Connect the client. + hs.Start() + defer hs.Close() + var f HttpCredentialsProviderFunc = func(ctx context.Context) (string, error) { + return "Bearer arbitrary_token", nil + } + ctx := context.WithValue(context.Background(), CtxCredentialsProvider, f) + authenticatedClient, err := DialContext(ctx, "ws://"+hs.Listener.Addr().String()) + assert.NoError(t, err) + + err = authenticatedClient.CallContext(context.Background(), nil, "arbitrary_call") + assert.EqualError(t, err, "arbitrary_call - access denied") +} diff --git a/rpc/handler.go b/rpc/handler.go index 23023eaca1..352c965bed 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -316,7 +316,19 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess } // handleCall processes method calls. +// Quorum: +// This is where server handle the call requests hence we enforce authorization check +// before the actual processing of the call. It also populates context with preauthenticated +// token so the responsible RPC method can leverage if needed (e.g: in multi tenancy) func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage { + if r, ok := h.conn.(securityContextResolver); ok { + if err := secureCall(r, msg); err != nil { + return securityErrorMessage(msg, err) + } + secCtx := r.Resolve() + h.log.Debug("Enrich call context with token from security context") + cp.ctx = context.WithValue(cp.ctx, CtxPreauthenticatedToken, secCtx.Value(CtxPreauthenticatedToken)) + } if msg.isSubscribe() { return h.handleSubscribe(cp, msg) } @@ -386,7 +398,11 @@ func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMes // runMethod runs the Go callback for an RPC method. func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *callback, args []reflect.Value) *jsonrpcMessage { - result, err := callb.call(ctx, msg.Method, args) + //Quorum + //Pass the request ID to the method as part of the context, in case the method needs it later + contextWithId := context.WithValue(ctx, "id", &msg.ID) + //End-Quorum + result, err := callb.call(contextWithId, msg.Method, args) if err != nil { return msg.errorResponse(err) } diff --git a/rpc/http.go b/rpc/http.go index 78fbd9f8f3..9133f1fc74 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -28,6 +28,8 @@ import ( "net/http" "sync" "time" + + "github.com/ethereum/go-ethereum/log" ) const ( @@ -43,6 +45,10 @@ type httpConn struct { req *http.Request closeOnce sync.Once closeCh chan interface{} + + // Quorum + // To return value being populated in Authorization request header + credentialsProvider HttpCredentialsProviderFunc } // httpConn is treated specially by Client. @@ -67,6 +73,15 @@ func (hc *httpConn) closed() <-chan interface{} { return hc.closeCh } +func (hc *httpConn) Configure(_ securityContext) { + // Client doesn't need to implement this +} + +func (hc *httpConn) Resolve() securityContext { + // Client doesn't need to implement this + return context.Background() +} + // HTTPTimeouts represents the configuration params for the HTTP RPC server. type HTTPTimeouts struct { // ReadTimeout is the maximum duration for reading the entire @@ -161,6 +176,10 @@ func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonr return nil } +// Quorum +// +// Populate Authorization request header with value from credentials provider +// Ignore if provider is unable to return the value func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { body, err := json.Marshal(msg) if err != nil { @@ -169,7 +188,13 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos req := hc.req.WithContext(ctx) req.Body = ioutil.NopCloser(bytes.NewReader(body)) req.ContentLength = int64(len(body)) - + if hc.credentialsProvider != nil { + if token, err := hc.credentialsProvider(ctx); err != nil { + log.Warn("unable to obtain http credentials from provider", "err", err) + } else { + req.Header.Set(HttpAuthorizationHeader, token) + } + } resp, err := hc.client.Do(req) if err != nil { return nil, err @@ -228,10 +253,10 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if origin := r.Header.Get("Origin"); origin != "" { ctx = context.WithValue(ctx, "Origin", origin) } - w.Header().Set("content-type", contentType) codec := newHTTPServerConn(r, w) defer codec.close() + s.authenticateHttpRequest(r, codec) s.serveSingleRequest(ctx, codec) } diff --git a/rpc/inproc.go b/rpc/inproc.go index fbe9a40cec..f1a977cb03 100644 --- a/rpc/inproc.go +++ b/rpc/inproc.go @@ -21,6 +21,9 @@ import ( "net" ) +type InProcServerReadyEvent struct { +} + // DialInProc attaches an in-process connection to the given RPC server. func DialInProc(handler *Server) *Client { initctx := context.Background() diff --git a/rpc/json.go b/rpc/json.go index 3be5d55f48..b7bf8d4a79 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -171,6 +171,10 @@ type jsonCodec struct { encMu sync.Mutex // guards the encoder encode func(v interface{}) error // encoder to allow multiple transports conn deadlineCloser + + // Quorum + // holding the security context for underlying connection + secCtx securityContext } // NewFuncCodec creates a codec which uses the given functions to read and write. If conn @@ -182,6 +186,7 @@ func NewFuncCodec(conn deadlineCloser, encode, decode func(v interface{}) error) encode: encode, decode: decode, conn: conn, + secCtx: context.Background(), } if ra, ok := conn.(ConnRemoteAddr); ok { codec.remote = ra.RemoteAddr() @@ -237,6 +242,14 @@ func (c *jsonCodec) closed() <-chan interface{} { return c.closeCh } +func (c *jsonCodec) Configure(secCtx securityContext) { + c.secCtx = secCtx +} + +func (c *jsonCodec) Resolve() securityContext { + return c.secCtx +} + // parseMessage parses raw bytes as a (batch of) JSON-RPC message(s). There are no error // checks in this function because the raw message has already been syntax-checked when it // is called. Any non-JSON-RPC messages in the input return the zero value of diff --git a/rpc/security.go b/rpc/security.go new file mode 100644 index 0000000000..81a6da1875 --- /dev/null +++ b/rpc/security.go @@ -0,0 +1,117 @@ +package rpc + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/golang/protobuf/ptypes" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" +) + +type securityContextKey string +type securityContext context.Context + +const ( + HttpAuthorizationHeader = "Authorization" + // this key is exported for WS transport + CtxCredentialsProvider = securityContextKey("CREDENTIALS_PROVIDER") // key to save reference to rpc.HttpCredentialsProviderFunc + // keys used to save values in request context + ctxAuthenticationError = securityContextKey("AUTHENTICATION_ERROR") // key to save error during authentication before processing the request body + CtxPreauthenticatedToken = securityContextKey("PREAUTHENTICATED_TOKEN") // key to save the preauthenticated token once authenticated +) + +type securityContextConfigurer interface { + Configure(secCtx securityContext) +} + +type securityContextResolver interface { + Resolve() securityContext +} + +type securityError struct{ message string } + +// Provider function to return token being injected in Authorization http request header +type HttpCredentialsProviderFunc func(ctx context.Context) (string, error) + +func (e *securityError) ErrorCode() int { return -32001 } + +func (e *securityError) Error() string { return e.message } + +func extractToken(req *http.Request) (string, bool) { + token := req.Header.Get(HttpAuthorizationHeader) + return token, token != "" +} + +func verifyExpiration(token *proto.PreAuthenticatedAuthenticationToken) error { + if token == nil { + return nil + } + expiredAt, err := ptypes.Timestamp(token.ExpiredAt) + if err != nil { + return fmt.Errorf("invalid timestamp in token: %s", err) + } + if time.Now().Before(expiredAt) { + return nil + } + return &securityError{"token expired"} +} + +func verifyAccess(service, method string, authorities []*proto.GrantedAuthority) error { + for _, authority := range authorities { + if authority.Service == "*" && authority.Method == "*" { + return nil + } + if authority.Service == "*" && authority.Method == method { + return nil + } + if authority.Service == service && authority.Method == "*" { + return nil + } + if authority.Service == service && authority.Method == method { + return nil + } + } + return &securityError{fmt.Sprintf("%s%s%s - access denied", service, serviceMethodSeparator, method)} +} + +// verify if a call is authorized using information available in the security context +// it also checks for token expiration. That means if this is called multiple times (batch processing), +// token expiration is checked multiple times. +func secureCall(resolver securityContextResolver, msg *jsonrpcMessage) error { + secCtx := resolver.Resolve() + if secCtx == nil { + return nil + } + if err, hasError := secCtx.Value(ctxAuthenticationError).(error); hasError { + return err + } + if authToken, isPreauthenticated := secCtx.Value(CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken); isPreauthenticated { + if err := verifyExpiration(authToken); err != nil { + return err + } + elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2) + if len(elem) != 2 { + log.Warn("unsupported method when performing authorization check", "method", msg.Method) + } else if err := verifyAccess(elem[0], elem[1], authToken.Authorities); err != nil { + return err + } + } + return nil +} + +// construct JSON RPC error message which has the ID of the request +func securityErrorMessage(forMsg *jsonrpcMessage, err error) *jsonrpcMessage { + msg := &jsonrpcMessage{Version: vsn, ID: forMsg.ID, Error: &jsonError{ + Code: defaultErrorCode, + Message: err.Error(), + }} + ec, ok := err.(Error) + if ok { + msg.Error.Code = ec.ErrorCode() + } + return msg +} diff --git a/rpc/security_test.go b/rpc/security_test.go new file mode 100644 index 0000000000..5d6ea9e019 --- /dev/null +++ b/rpc/security_test.go @@ -0,0 +1,219 @@ +package rpc + +import ( + "context" + "errors" + "net/http" + "testing" + "time" + + "github.com/golang/protobuf/ptypes" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + testifyassert "github.com/stretchr/testify/assert" +) + +func TestVerifyAccess_whenNotMatch(t *testing.T) { + assert := testifyassert.New(t) + + assert.Error(verifyAccess("xyz", "abc", []*proto.GrantedAuthority{ + { + Service: "bar", + Method: "foo", + }, + })) +} + +func TestVerifyAccess_whenEmpty(t *testing.T) { + assert := testifyassert.New(t) + + assert.Error(verifyAccess("xyz", "abc", nil)) +} + +func TestVerifyAccess_whenExactMatch(t *testing.T) { + assert := testifyassert.New(t) + + assert.NoError(verifyAccess("bar", "foo", []*proto.GrantedAuthority{ + { + Service: "xyz", + Method: "abc", + }, + { + Service: "bar", + Method: "foo", + }, + })) +} + +func TestVerifyAccess_whenWildcardServiceMatch(t *testing.T) { + assert := testifyassert.New(t) + + assert.NoError(verifyAccess("bar", "foo", []*proto.GrantedAuthority{ + { + Service: "xyz", + Method: "abc", + }, + { + Service: "*", + Method: "foo", + }, + })) +} + +func TestVerifyAccess_whenWildcardMethodMatch(t *testing.T) { + assert := testifyassert.New(t) + + assert.NoError(verifyAccess("bar", "foo", []*proto.GrantedAuthority{ + { + Service: "xyz", + Method: "abc", + }, + { + Service: "bar", + Method: "*", + }, + })) +} + +func TestVerifyAccess_whenWildcardMatch(t *testing.T) { + assert := testifyassert.New(t) + + assert.NoError(verifyAccess("bar", "foo", []*proto.GrantedAuthority{ + { + Service: "*", + Method: "*", + }, + })) +} + +func TestVerifyExpiration_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + expiredAt, _ := ptypes.TimestampProto(time.Now().Add(1 * time.Minute)) + assert.NoError(verifyExpiration(&proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + })) +} + +func TestVerifyExpiration_whenExpired(t *testing.T) { + assert := testifyassert.New(t) + expiredAt, _ := ptypes.TimestampProto(time.Now().Add(-1 * time.Minute)) + assert.Error(verifyExpiration(&proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + })) +} + +func TestExtractToken_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + req, _ := http.NewRequest("POST", "", nil) + arbitraryValue := "xyz" + req.Header.Set(HttpAuthorizationHeader, arbitraryValue) + + token, ok := extractToken(req) + + assert.True(ok) + assert.Equal(arbitraryValue, token) +} + +func TestExtractToken_whenEmpty(t *testing.T) { + assert := testifyassert.New(t) + req, _ := http.NewRequest("POST", "", nil) + + _, ok := extractToken(req) + + assert.False(ok) +} + +func TestSecureCall_whenThereIsAuthenticationError(t *testing.T) { + assert := testifyassert.New(t) + arbitraryError := errors.New("arbitrary error") + stubSecurityContextResolver := newStubSecurityContextResolver([]struct{ k, v interface{} }{ + {ctxAuthenticationError, arbitraryError}, + }) + + err := secureCall(stubSecurityContextResolver, &jsonrpcMessage{}) + + assert.EqualError(err, arbitraryError.Error()) +} + +func TestSecureCall_whenTokenExpired(t *testing.T) { + assert := testifyassert.New(t) + expiredAt, _ := ptypes.TimestampProto(time.Now().Add(-1 * time.Hour)) + stubSecurityContextResolver := newStubSecurityContextResolver([]struct{ k, v interface{} }{ + {CtxPreauthenticatedToken, &proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + }}, + }) + + err := secureCall(stubSecurityContextResolver, &jsonrpcMessage{}) + + assert.EqualError(err, "token expired") +} + +func TestSecureCall_whenTypical(t *testing.T) { + assert := testifyassert.New(t) + expiredAt, _ := ptypes.TimestampProto(time.Now().Add(1 * time.Hour)) + stubSecurityContextResolver := newStubSecurityContextResolver([]struct{ k, v interface{} }{ + {CtxPreauthenticatedToken, &proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + Authorities: []*proto.GrantedAuthority{ + { + Service: "eth", + Method: "blockNumber", + }, + }, + }}, + }) + + err := secureCall(stubSecurityContextResolver, &jsonrpcMessage{Method: "eth_blockNumber"}) + + assert.NoError(err) +} + +func TestSecureCall_whenAccessDenied(t *testing.T) { + assert := testifyassert.New(t) + expiredAt, _ := ptypes.TimestampProto(time.Now().Add(1 * time.Hour)) + stubSecurityContextResolver := newStubSecurityContextResolver([]struct{ k, v interface{} }{ + {CtxPreauthenticatedToken, &proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + Authorities: []*proto.GrantedAuthority{ + { + Service: "eth", + Method: "blockNumber", + }, + }, + }}, + }) + + err := secureCall(stubSecurityContextResolver, &jsonrpcMessage{Method: "eth_someMethod"}) + + assert.EqualError(err, "eth_someMethod - access denied") +} + +func TestSecureCall_whenMethodInJSONMessageIsNotSupported(t *testing.T) { + assert := testifyassert.New(t) + expiredAt, _ := ptypes.TimestampProto(time.Now().Add(1 * time.Hour)) + stubSecurityContextResolver := newStubSecurityContextResolver([]struct{ k, v interface{} }{ + {CtxPreauthenticatedToken, &proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + }}, + }) + + err := secureCall(stubSecurityContextResolver, &jsonrpcMessage{Method: "arbitrary method"}) + + assert.NoError(err) +} + +type stubSecurityContextResolver struct { + ctx securityContext +} + +func newStubSecurityContextResolver(ctx []struct{ k, v interface{} }) *stubSecurityContextResolver { + sc := securityContext(context.Background()) + for _, kv := range ctx { + sc = context.WithValue(sc, kv.k, kv.v) + } + return &stubSecurityContextResolver{sc} +} + +func (sr *stubSecurityContextResolver) Resolve() securityContext { + return sr.ctx +} diff --git a/rpc/server.go b/rpc/server.go index 64e078a7fd..29ed835771 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -19,10 +19,12 @@ package rpc import ( "context" "io" + "net/http" "sync/atomic" mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/plugin/security" ) const MetadataApi = "rpc" @@ -46,11 +48,25 @@ type Server struct { idgen func() ID run int32 codecs mapset.Set + + // Quorum + // The implementation would authenticate the token coming from a request + authenticationManager security.AuthenticationManager +} + +// Quorum +// Create a server which is protected by authManager +func NewProtectedServer(authManager security.AuthenticationManager) *Server { + server := NewServer() + if authManager != nil { + server.authenticationManager = authManager + } + return server } // NewServer creates a new server instance with no registered handlers. func NewServer() *Server { - server := &Server{idgen: randomIDGenerator(), codecs: mapset.NewSet(), run: 1} + server := &Server{idgen: randomIDGenerator(), codecs: mapset.NewSet(), run: 1, authenticationManager: security.NewDisabledAuthenticationManager()} // Register the default service providing meta information about the RPC service such // as the services and methods it offers. rpcService := &RPCService{server} @@ -128,6 +144,33 @@ func (s *Server) Stop() { } } +// Quorum +// Perform authentication on the HTTP request. Populate security context with necessary information +// for subsequent authorization-related activities +func (s *Server) authenticateHttpRequest(r *http.Request, cfg securityContextConfigurer) { + securityContext := context.Background() + defer func() { + cfg.Configure(securityContext) + }() + if isAuthEnabled, err := s.authenticationManager.IsEnabled(context.Background()); err != nil { + // this indicates a failure in the plugin. We don't want any subsequent request unchecked + log.Error("failure when checking if authentication manager is enabled", "err", err) + securityContext = context.WithValue(securityContext, ctxAuthenticationError, &securityError{"internal error"}) + return + } else if !isAuthEnabled { + return + } + if token, hasToken := extractToken(r); hasToken { + if authToken, err := s.authenticationManager.Authenticate(context.Background(), token); err != nil { + securityContext = context.WithValue(securityContext, ctxAuthenticationError, &securityError{err.Error()}) + } else { + securityContext = context.WithValue(securityContext, CtxPreauthenticatedToken, authToken) + } + } else { + securityContext = context.WithValue(securityContext, ctxAuthenticationError, &securityError{"missing access token"}) + } +} + // RPCService gives meta information about the server. // e.g. gives information about the loaded modules. type RPCService struct { diff --git a/rpc/server_test.go b/rpc/server_test.go index 6a2b09e449..88bbf4bc5c 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -19,13 +19,20 @@ package rpc import ( "bufio" "bytes" + "context" + "errors" "io" "io/ioutil" "net" + "net/http" "path/filepath" "strings" "testing" "time" + + "github.com/golang/protobuf/ptypes" + "github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto" + "github.com/stretchr/testify/assert" ) func TestServerRegisterName(t *testing.T) { @@ -46,6 +53,9 @@ func TestServerRegisterName(t *testing.T) { } wantCallbacks := 9 + // Quorum - Add extra callback for the function added by us EchoCtxId + wantCallbacks += 1 + // End Quorum if len(svc.callbacks) != wantCallbacks { t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks)) } @@ -150,3 +160,117 @@ func TestServerShortLivedConn(t *testing.T) { } } } + +func TestAuthenticateHttpRequest_whenAuthenticationManagerFails(t *testing.T) { + protectedServer := NewProtectedServer(&stubAuthenticationManager{false, errors.New("arbitrary error")}) + arbitraryRequest, _ := http.NewRequest("POST", "https://arbitraryUrl", nil) + captor := &securityContextConfigurerCaptor{} + + protectedServer.authenticateHttpRequest(arbitraryRequest, captor) + + actualErr, hasError := captor.context.Value(ctxAuthenticationError).(error) + assert.True(t, hasError, "must have error") + assert.EqualError(t, actualErr, "internal error") + _, hasAuthToken := captor.context.Value(CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken) + assert.False(t, hasAuthToken, "must not be preauthenticated") +} + +func TestAuthenticateHttpRequest_whenTypical(t *testing.T) { + protectedServer := NewProtectedServer(&stubAuthenticationManager{true, nil}) + arbitraryRequest, _ := http.NewRequest("POST", "https://arbitraryUrl", nil) + arbitraryRequest.Header.Set(HttpAuthorizationHeader, "arbitrary value") + captor := &securityContextConfigurerCaptor{} + + protectedServer.authenticateHttpRequest(arbitraryRequest, captor) + + _, hasError := captor.context.Value(ctxAuthenticationError).(error) + assert.False(t, hasError, "must not have error") + _, hasAuthToken := captor.context.Value(CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken) + assert.True(t, hasAuthToken, "must be preauthenticated") +} + +func TestAuthenticateHttpRequest_whenAuthenticationManagerIsDisabled(t *testing.T) { + protectedServer := NewProtectedServer(&stubAuthenticationManager{false, nil}) + arbitraryRequest, _ := http.NewRequest("POST", "https://arbitraryUrl", nil) + captor := &securityContextConfigurerCaptor{} + + protectedServer.authenticateHttpRequest(arbitraryRequest, captor) + + _, hasError := captor.context.Value(ctxAuthenticationError).(error) + assert.False(t, hasError, "must not have error") + _, hasAuthToken := captor.context.Value(CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken) + assert.False(t, hasAuthToken, "must not be preauthenticated") +} + +func TestAuthenticateHttpRequest_whenMissingAccessToken(t *testing.T) { + protectedServer := NewProtectedServer(&stubAuthenticationManager{true, nil}) + arbitraryRequest, _ := http.NewRequest("POST", "https://arbitraryUrl", nil) + captor := &securityContextConfigurerCaptor{} + + protectedServer.authenticateHttpRequest(arbitraryRequest, captor) + + actualErr, hasError := captor.context.Value(ctxAuthenticationError).(error) + assert.True(t, hasError, "must have error") + assert.EqualError(t, actualErr, "missing access token") + _, hasAuthToken := captor.context.Value(CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken) + assert.False(t, hasAuthToken, "must not be preauthenticated") +} + +type securityContextConfigurerCaptor struct { + context securityContext +} + +func (sc *securityContextConfigurerCaptor) Configure(secCtx securityContext) { + sc.context = secCtx +} + +type stubAuthenticationManager struct { + isEnabled bool + stubErr error +} + +func (s *stubAuthenticationManager) Authenticate(_ context.Context, _ string) (*proto.PreAuthenticatedAuthenticationToken, error) { + expiredAt, err := ptypes.TimestampProto(time.Now().Add(1 * time.Hour)) + if err != nil { + return nil, err + } + return &proto.PreAuthenticatedAuthenticationToken{ + ExpiredAt: expiredAt, + }, nil +} + +func (s *stubAuthenticationManager) IsEnabled(_ context.Context) (bool, error) { + return s.isEnabled, s.stubErr +} + +// Quorum - This test checks that the `ID` from the RPC call is passed to the handler method +func TestServerContextIdCaptured(t *testing.T) { + var ( + request = `{"jsonrpc":"2.0","id":1,"method":"test_echoCtxId"}` + "\n" + wantResp = `{"jsonrpc":"2.0","id":1,"result":1}` + "\n" + ) + + server := newTestServer() + defer server.Stop() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal("can't listen:", err) + } + defer listener.Close() + go server.ServeListener(listener) + + conn, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + t.Fatal("can't dial:", err) + } + defer conn.Close() + // Write the request, then half-close the connection so the server stops reading. + conn.Write([]byte(request)) + conn.(*net.TCPConn).CloseWrite() + // Now try to get the response. + buf := make([]byte, 2000) + n, err := conn.Read(buf) + + assert.NoErrorf(t, err, "read error:", err) + assert.Equalf(t, buf[:n], []byte(wantResp), "wrong response: %s", buf[:n]) +} diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index 6f948a1bac..0b6a2b2fcd 100644 --- a/rpc/testservice_test.go +++ b/rpc/testservice_test.go @@ -79,6 +79,10 @@ func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args * return echoResult{str, i, args} } +func (s *testService) EchoCtxId(ctx context.Context) interface{} { + return ctx.Value("id") +} + func (s *testService) Sleep(ctx context.Context, duration time.Duration) { time.Sleep(duration) } diff --git a/rpc/types.go b/rpc/types.go index bab1b3957b..8030347eee 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -50,10 +50,16 @@ type DataError interface { // ServerCodec implements reading, parsing and writing RPC messages for the server side of // a RPC session. Implementations must be go-routine safe since the codec can be called in // multiple go-routines concurrently. +// Quorum: +// As ServerCodec is used in both client and server implementation, we extend it with the interfaces +// securityContextConfigurer & securityContextResolver to hold authorization-related information +// which is then used by rpc/handler to enforce the security type ServerCodec interface { readBatch() (msgs []*jsonrpcMessage, isBatch bool, err error) close() jsonWriter + securityContextConfigurer + securityContextResolver } // jsonWriter can write JSON messages to its underlying connection. diff --git a/rpc/types_test.go b/rpc/types_test.go index 89b0c9171a..29d4baab35 100644 --- a/rpc/types_test.go +++ b/rpc/types_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The go-ethereum Authors +// Copyright 2017 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 diff --git a/rpc/websocket.go b/rpc/websocket.go index a716383be9..ca4e9d9ecf 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -18,6 +18,7 @@ package rpc import ( "context" + "crypto/tls" "encoding/base64" "fmt" "net/http" @@ -59,6 +60,7 @@ func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler { return } codec := newWebsocketCodec(conn) + s.authenticateHttpRequest(r, codec) s.ServeCodec(codec, 0) }) } @@ -81,8 +83,10 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { // allow localhost if no allowedOrigins are specified. if len(origins.ToSlice()) == 0 { origins.Add("http://localhost") + origins.Add("https://localhost") if hostname, err := os.Hostname(); err == nil { origins.Add("http://" + strings.ToLower(hostname)) + origins.Add("https://" + strings.ToLower(hostname)) } } log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice())) @@ -122,12 +126,46 @@ func (e wsHandshakeError) Error() string { // DialWebsocketWithDialer creates a new RPC client that communicates with a JSON-RPC server // that is listening on the given endpoint using the provided dialer. +// +// The context is used for the initial connection establishment. It does not +// affect subsequent interactions with the client. func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, dialer websocket.Dialer) (*Client, error) { + return DialWebsocketWithCustomTLS(ctx, endpoint, origin, nil) +} + +// Quorum +// +// DialWebsocketWithCustomTLS creates a new RPC client that communicates with a JSON-RPC server +// that is listening on the given endpoint. +// At the same time, allowing to customize TLSClientConfig of the dialer +// +// The context is used for the initial connection establishment. It does not +// affect subsequent interactions with the client. +func DialWebsocketWithCustomTLS(ctx context.Context, endpoint, origin string, tlsConfig *tls.Config) (*Client, error) { + dialer := websocket.Dialer{ + ReadBufferSize: wsReadBuffer, + WriteBufferSize: wsWriteBuffer, + WriteBufferPool: wsBufferPool, + } + endpoint, header, err := wsClientHeaders(endpoint, origin) if err != nil { return nil, err } + if tlsConfig != nil { + dialer.TLSClientConfig = tlsConfig + } + + credProviderFunc, hasCredProviderFunc := ctx.Value(CtxCredentialsProvider).(HttpCredentialsProviderFunc) return newClient(ctx, func(ctx context.Context) (ServerCodec, error) { + if hasCredProviderFunc { + token, err := credProviderFunc(ctx) + if err != nil { + log.Warn("unable to obtain credentials from provider", "err", err) + } else { + header.Set(HttpAuthorizationHeader, token) + } + } conn, resp, err := dialer.DialContext(ctx, endpoint, header) if err != nil { hErr := wsHandshakeError{err: err} @@ -165,7 +203,7 @@ func wsClientHeaders(endpoint, origin string) (string, http.Header, error) { } if endpointURL.User != nil { b64auth := base64.StdEncoding.EncodeToString([]byte(endpointURL.User.String())) - header.Add("authorization", "Basic "+b64auth) + header.Add(HttpAuthorizationHeader, "Basic "+b64auth) endpointURL.User = nil } return endpointURL.String(), header, nil diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index f54fc3cd54..2d60d533fc 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -39,7 +39,7 @@ func TestWebsocketClientHeaders(t *testing.T) { if endpoint != "wss://example.com:1234" { t.Fatal("User should have been stripped from the URL") } - if header.Get("authorization") != "Basic dGVzdHVzZXI6dGVzdC1QQVNTXzAx" { + if header.Get(HttpAuthorizationHeader) != "Basic dGVzdHVzZXI6dGVzdC1QQVNTXzAx" { t.Fatal("Basic auth header is incorrect") } if header.Get("origin") != "https://example.com" { diff --git a/signer/core/api.go b/signer/core/api.go index 7e6ece997f..59430c76f1 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -27,12 +27,14 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/pluggable" "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/plugin" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/signer/storage" ) @@ -125,7 +127,7 @@ type Metadata struct { Origin string `json:"Origin"` } -func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath string) *accounts.Manager { +func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, plugins *plugin.Settings, scpath string) *accounts.Manager { var ( backends []accounts.Backend n, p = keystore.StandardScryptN, keystore.StandardScryptP @@ -160,6 +162,14 @@ func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath str log.Debug("Trezor support enabled via WebUSB") } } + // + if plugins != nil { + if _, ok := plugins.Providers[plugin.AccountPluginInterfaceName]; ok { + pluginBackend := pluggable.NewBackend() + backends = append(backends, pluginBackend) + } + } + // // Start a smart card hub if len(scpath) > 0 { @@ -509,11 +519,17 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth var ( err error result SignTxResponse + msgs *ValidationMessages ) - msgs, err := api.validator.ValidateTransaction(methodSelector, &args) - if err != nil { - return nil, err + if args.IsPrivate { + msgs = new(ValidationMessages) + } else { + msgs, err = api.validator.ValidateTransaction(methodSelector, &args) + if err != nil { + return nil, err + } } + // If we are in 'rejectMode', then reject rather than show the user warnings if api.rejectMode { if err := msgs.getWarnings(); err != nil { diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 800020b0cf..97258c6737 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -125,7 +125,7 @@ func setup(t *testing.T) (*core.SignerAPI, *headlessUi) { t.Fatal(err.Error()) } ui := &headlessUi{make(chan string, 20), make(chan string, 20)} - am := core.StartClefAccountManager(tmpDirName(t), true, true, "") + am := core.StartClefAccountManager(tmpDirName(t), true, true, nil, "") api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{}) return api, ui diff --git a/signer/core/plugin_api.go b/signer/core/plugin_api.go new file mode 100644 index 0000000000..d036f953d3 --- /dev/null +++ b/signer/core/plugin_api.go @@ -0,0 +1,39 @@ +package core + +import ( + "context" + "errors" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/plugin/account" +) + +// + +type approvalCreatorService struct { + creator account.CreatorService + ui UIClientAPI +} + +// NewApprovalCreatorService adds a wrapper to the provided creator service which requires UI approval before executing the service's methods +func NewApprovalCreatorService(creator account.CreatorService, ui UIClientAPI) account.CreatorService { + return &approvalCreatorService{ + creator: creator, + ui: ui, + } +} + +func (s *approvalCreatorService) NewAccount(ctx context.Context, newAccountConfig interface{}) (accounts.Account, error) { + if resp, err := s.ui.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil { + return accounts.Account{}, err + } else if !resp.Approved { + return accounts.Account{}, ErrRequestDenied + } + + return s.creator.NewAccount(ctx, newAccountConfig) +} + +// ImportRawKey is unsupported in the clef external API for parity with the available keystore account functionality +func (s *approvalCreatorService) ImportRawKey(_ context.Context, _ string, _ interface{}) (accounts.Account, error) { + return accounts.Account{}, errors.New("not supported") +} diff --git a/signer/core/types.go b/signer/core/types.go index 58b377c8d8..354f53c8ac 100644 --- a/signer/core/types.go +++ b/signer/core/types.go @@ -76,6 +76,9 @@ type SendTxArgs struct { // We accept "data" and "input" for backwards-compatibility reasons. Data *hexutil.Bytes `json:"data"` Input *hexutil.Bytes `json:"input,omitempty"` + // QUORUM + IsPrivate bool `json:"isPrivate,omitempty"` + // END QUORUM } func (args SendTxArgs) String() string { @@ -86,7 +89,7 @@ func (args SendTxArgs) String() string { return err.Error() } -func (args *SendTxArgs) toTransaction() *types.Transaction { +func (args *SendTxArgs) toTransaction() (tx *types.Transaction) { var input []byte if args.Data != nil { input = *args.Data @@ -94,7 +97,12 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { input = *args.Input } if args.To == nil { - return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input) + tx = types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input) + } else { + tx = types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input) } - return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input) + if args.IsPrivate { + tx.SetPrivate() + } + return } diff --git a/tests/block_test.go b/tests/block_test.go index 8fa90e3e39..a87d194e1a 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -44,6 +44,12 @@ func TestBlockchain(t *testing.T) { // using 4.6 TGas bt.skipLoad(`.*randomStatetest94.json.*`) + //Quorum - uncles are not in scope for quorum + bt.skipLoad(`.*bcUncleSpecialTests/futureUncleTimestampDifficultyDrop4\.json`) + bt.skipLoad(`.*bcUncleSpecialTests/futureUncleTimestampDifficultyDrop3\.json`) + bt.skipLoad(`.*bcUncleSpecialTests/futureUncleTimestampDifficultyDrop2\.json`) + bt.skipLoad(`.*bcUncleSpecialTests/futureUncleTimestampDifficultyDrop\.json`) + bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) diff --git a/tests/block_test_util.go b/tests/block_test_util.go index be9cdb70cd..124f6d69d2 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -138,7 +138,7 @@ func (t *BlockTest) Run(snapshotter bool) error { if common.Hash(t.json.BestBlock) != cmlast { return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast) } - newDB, err := chain.State() + newDB, _, err := chain.State() if err != nil { return err } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index a999cba471..ef893a2da8 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -184,7 +184,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh } context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash - evm := vm.NewEVM(context, statedb, config, vmconfig) + evm := vm.NewEVM(context, statedb, statedb, config, vmconfig) gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) diff --git a/tests/transaction_test.go b/tests/transaction_test.go index 0e3670d04b..20123bb043 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -32,6 +32,23 @@ func TestTransaction(t *testing.T) { // This is a pseudo-consensus vulnerability, but not in practice // because of the gas limit txt.skipLoad("^ttGasLimit/TransactionWithGasLimitxPriceOverflow.json") + + //Quorum - skip the tests below as they have V=37/38 for transactions being tested and it is causing quorum to use quorum private signer for public transactions + txt.skipLoad("^ttGasLimit/TransactionWithHihghGasLimit63m1.json") + txt.skipLoad("^ttVValue/V_equals38.json") + txt.skipLoad("^ttVValue/V_equals37.json") + txt.skipLoad("^ttSignature/Vitalik_9.json") + txt.skipLoad("^ttSignature/Vitalik_8.json") + txt.skipLoad("^ttSignature/Vitalik_7.json") + txt.skipLoad("^ttSignature/Vitalik_6.json") + txt.skipLoad("^ttSignature/Vitalik_5.json") + txt.skipLoad("^ttSignature/Vitalik_4.json") + txt.skipLoad("^ttSignature/Vitalik_3.json") + txt.skipLoad("^ttSignature/Vitalik_2.json") + txt.skipLoad("^ttSignature/Vitalik_11.json") + txt.skipLoad("^ttSignature/Vitalik_10.json") + txt.skipLoad("^ttSignature/Vitalik_1.json") + // We _do_ allow more than uint64 in gas price, as opposed to the tests // This is also not a concern, as long as tx.Cost() uses big.Int for // calculating the final cozt diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index ad124b7b25..fa54ef66c9 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -151,7 +151,7 @@ func (t *VMTest) newEVM(statedb *state.StateDB, vmconfig vm.Config) *vm.EVM { GasPrice: t.json.Exec.GasPrice, } vmconfig.NoRecursion = true - return vm.NewEVM(context, statedb, params.MainnetChainConfig, vmconfig) + return vm.NewEVM(context, statedb, statedb, params.MainnetChainConfig, vmconfig) } func vmTestBlockHash(n uint64) common.Hash { diff --git a/trie/database.go b/trie/database.go index 4f310a776c..3b611fceb0 100644 --- a/trie/database.go +++ b/trie/database.go @@ -135,6 +135,9 @@ type rawShortNode struct { Val node } +func (n rawShortNode) canUnload(uint16, uint16) bool { + panic("this should never end up in a live trie") +} func (n rawShortNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } func (n rawShortNode) fstring(ind string) string { panic("this should never end up in a live trie") } From fb709d68efea5b5b212618226c20a26756cc4e7e Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Tue, 23 Feb 2021 16:23:32 +0000 Subject: [PATCH 19/23] fix: stubs and duplication of unit tests --- core/tx_pool_test.go | 44 ------------------------------------- internal/ethapi/api_test.go | 2 +- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 3c33265f37..0ab6eccc96 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -2083,47 +2083,3 @@ func TestEIP155SignerOnTxPool(t *testing.T) { } } - -//Checks that the EIP155 signer is assigned to the TxPool no matter the configuration, even invalid config -func TestEIP155SignerOnTxPool(t *testing.T) { - var flagtests = []struct { - name string - homesteadBlock *big.Int - eip155Block *big.Int - }{ - {"hsnileip155nil", nil, nil}, - {"hsnileip1550", nil, big.NewInt(0)}, - {"hsnileip155100", nil, big.NewInt(100)}, - {"hs0eip155nil", big.NewInt(0), nil}, - {"hs0eip1550", big.NewInt(0), big.NewInt(0)}, - {"hs0eip155100", big.NewInt(0), big.NewInt(100)}, - {"hs100eip155nil", big.NewInt(100), nil}, - {"hs100eip1550", big.NewInt(100), big.NewInt(0)}, - {"hs100eip155100", big.NewInt(100), big.NewInt(100)}, - } - - for _, tt := range flagtests { - t.Run("", func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) - blockchain := &testBlockChain{statedb, statedb, 1000000, new(event.Feed)} - - chainconfig := ¶ms.ChainConfig{ - ChainID: big.NewInt(10), - HomesteadBlock: tt.homesteadBlock, - EIP150Block: big.NewInt(0), - EIP155Block: tt.eip155Block, - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - Ethash: new(params.EthashConfig), - } - - pool := NewTxPool(testTxPoolConfig, chainconfig, blockchain) - - if reflect.TypeOf(types.EIP155Signer{}) != reflect.TypeOf(pool.signer) { - t.Fail() - } - }) - } - -} diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 483cc0258d..ccf5ed9228 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -560,7 +560,7 @@ func (sb *StubBackend) GetReceipts(ctx context.Context, blockHash common.Hash) ( panic("implement me") } -func (sb *StubBackend) GetTd(blockHash common.Hash) *big.Int { +func (sb *StubBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { panic("implement me") } From b72d13b0ba72e9f38a3142d04c4f0c5221d6ccce Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Tue, 23 Feb 2021 16:29:45 +0000 Subject: [PATCH 20/23] fix: lint errors --- cmd/geth/usage.go | 1 + core/vm/evm.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index bbce032ac7..1e81a16383 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -30,6 +30,7 @@ import ( // Quorum var quorumAccountFlagGroup = "QUORUM ACCOUNT" + // End Quorum // AppHelpFlagGroups is the application flags, grouped by functionality. diff --git a/core/vm/evm.go b/core/vm/evm.go index 939d989f63..7a5be0c0bc 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -30,8 +30,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/multitenancy" "github.com/ethereum/go-ethereum/params" - "github.com/holiman/uint256" "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" ) // note: Quorum, States, and Value Transfer From 963d7546b3ad98d3f3d53ade005877e5fb8bdb3e Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Wed, 24 Feb 2021 14:04:57 +0000 Subject: [PATCH 21/23] fix: multitenancy validation during the EVM execution --- core/vm/evm.go | 38 +++++++++++++++++++++++++++++--------- core/vm/evm_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 7a5be0c0bc..b4f2d76ddd 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -79,17 +79,10 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { // Quorum if contract.CodeAddr != nil { - // Using CodeAddr is favour over contract.Address() // During DelegateCall() CodeAddr is the address of the delegated account address := *contract.CodeAddr - // during simulation/eth_call, when contract code is empty, there's no execution hence the - // multitenancy check will not happen in captureOperationMode(). - // This additional check to ensure we capture this case - if evm.SupportsMultitenancy && evm.AuthorizeMessageCallFunc != nil && len(contract.Code) == 0 { - return nil, multitenancy.ErrNotAuthorized - } - if err := evm.captureAffectedContract(address, ModeUnknown); err != nil { + if err := validateMultitenancy(evm, contract.Code, address); err != nil { return nil, err } // When delegatecall, need to capture the operation mode in the context of contract.Address() @@ -406,7 +399,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // The contract is a scoped environment for this execution context only. code := evm.StateDB.GetCode(addr) if len(code) == 0 { - ret, err = nil, nil // gas is unchanged + // Quorum - If the account has no code, we still check for multitenancy + ret, err = nil, validateMultitenancy(evm, code, addr) // gas is unchanged } else { addrCopy := addr // If the account has no code, we can abort here @@ -444,8 +438,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, return nil, gas, nil } + // Quorum evm.Push(getDualState(evm, addr)) defer func() { evm.Pop() }() + // End Quorum // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -491,8 +487,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by return nil, gas, nil } + // Quorum evm.Push(getDualState(evm, addr)) defer func() { evm.Pop() }() + // End Quorum // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -625,11 +623,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Create a new account on the state snapshot := evm.StateDB.Snapshot() + + // Quorum if evm.SupportsMultitenancy && evm.AuthorizeCreateFunc != nil { if authorized := evm.AuthorizeCreateFunc(); !authorized { return nil, common.Address{}, gas, multitenancy.ErrNotAuthorized } } + // End Quorum + evm.StateDB.CreateAccount(address) evm.affectedContracts[address] = newAffectedType(Creation, ModeWrite|ModeRead) if evm.chainRules.IsEIP158 { @@ -961,6 +963,8 @@ func (evm *EVM) AffectedContracts() []common.Address { return addr[:] } +// Quorum +// // Return MerkleRoot of all affected contracts (due to both creation and message call) func (evm *EVM) CalculateMerkleRoot() (common.Hash, error) { combined := new(trie.Trie) @@ -975,3 +979,19 @@ func (evm *EVM) CalculateMerkleRoot() (common.Hash, error) { } return combined.Hash(), nil } + +// Quorum +// +// validateMultitenancy - validates if multitenancy is authorized for this execution +func validateMultitenancy(evm *EVM, code []byte, address common.Address) error { + // during simulation/eth_call, when contract code is empty, there's no execution hence the + // multitenancy check will not happen in captureOperationMode(). + // This additional check to ensure we capture this case + if evm.SupportsMultitenancy && evm.AuthorizeMessageCallFunc != nil && len(code) == 0 { + return multitenancy.ErrNotAuthorized + } + if err := evm.captureAffectedContract(address, ModeUnknown); err != nil { + return err + } + return nil +} diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index e280e25e89..a8f602aff2 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -1,8 +1,16 @@ package vm import ( + "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/multitenancy" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/assert" ) @@ -21,3 +29,27 @@ func TestAffectedMode_Update_whenTypical(t *testing.T) { } } } + +func TestCall_shouldReturnErrorNotAuthorized_whenNoCodeAndSupportsMultitenancy(t *testing.T) { + address := common.BytesToAddress([]byte("contract")) + + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(address) + statedb.SetCode(address, []byte{}) + statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{})) + statedb.Finalise(true) // Push the state into the "original" slot + + vmctx := Context{ + CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true }, + Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, + } + vmenv := NewEVM(vmctx, statedb, statedb, params.QuorumTestChainConfig, Config{ExtraEips: []int{2200}}) + vmenv.SupportsMultitenancy = true + vmenv.AuthorizeMessageCallFunc = func(contractAddress common.Address) (bool, bool, error) { + return true, true, nil + } + + _, _, err := vmenv.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(big.Int)) + + assert.Error(t, err, multitenancy.ErrNotAuthorized) +} From 9d65c796d90509d531cc3e433669bf283aa73a98 Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Wed, 24 Feb 2021 17:37:27 +0000 Subject: [PATCH 22/23] tidy: remove TODOs --- core/blockchain.go | 1 - internal/ethapi/api.go | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 6b86b39694..72c8e1e4bd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1509,7 +1509,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } // Explicit commit for privateStateTriedb privateTriedb := bc.privateStateCache.TrieDB() - // TODO ricardolyn: check if nil argument is fine if err := privateTriedb.Commit(privateRoot, false, nil); err != nil { return NonStatTy, err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d15b213114..e98042ac7b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -2185,22 +2185,22 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen gas := (hexutil.Uint64)(90000) args.Gas = &gas } - // /Quorum + // End Quorum + if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } - //TODO ricardolyn: evaluate if this change affects Quorum 0 gas price - // Before actually sign the transaction, ensure the transaction fee is reasonable. if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { return nil, err } + // Quorum toSign := args.toTransaction() - if args.IsPrivate() { toSign.SetPrivate() } - // /Quorum + // End Quorum + tx, err := s.sign(args.From, toSign) if err != nil { return nil, err From 6a068a115edec5d6a529670c0c194cfe3c2820f0 Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Fri, 26 Feb 2021 10:08:13 +0000 Subject: [PATCH 23/23] tidy: renamed to match better the standard --- core/vm/evm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index a8f602aff2..b1475ae0bd 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -30,7 +30,7 @@ func TestAffectedMode_Update_whenTypical(t *testing.T) { } } -func TestCall_shouldReturnErrorNotAuthorized_whenNoCodeAndSupportsMultitenancy(t *testing.T) { +func TestCall_NoContractCodeAndIsMultitenancy_ErrorNotAuthorized(t *testing.T) { address := common.BytesToAddress([]byte("contract")) statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)

dS0! 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%0D|XM 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 {d;n=$T`cdAK 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