diff --git a/README.md b/README.md index 6509b245a..39a688ca2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Quorum +# Quorum Slack @@ -8,127 +8,38 @@ Quorum is a fork of [go-ethereum](https://github.com/ethereum/go-ethereum) and i Key enhancements over go-ethereum: - * __Privacy__ - Quorum supports private transactions and private contracts through public/private state separation and utilising [Constellation](https://github.com/jpmorganchase/constellation), a peer-to-peer encrypted message exchange for directed transfer of private data to network participants - * __Alternative Consensus Mechanisms__ - with no need for POW/POS in a permissioned network, Quorum instead offers multiple consensus mechanisms that are more appropriate for consortium chains: +* __Privacy__ - 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/jpmorganchase/constellation) and [Tessera](https://github.com/jpmorganchase/tessera)) for directed transfer of private data to network participants +* __Alternative Consensus Mechanisms__ - 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__ - a consensus model for faster blocktimes, transaction finality, and on-demand block creation * __Istanbul BFT__ - a PBFT-inspired consensus algorithm with transaction finality, by AMIS. - * __Peer Permissioning__ - node/peer permissioning using smart contracts, ensuring only known parties can join the network - * __Higher Performance__ - Quorum offers significantly higher performance than public geth - -Note: The QuorumChain consensus algorithm is not yet supported by this release. +* __Peer Permissioning__ - node/peer permissioning using smart contracts, ensuring only known parties can join the network +* __Higher Performance__ - Quorum offers significantly higher performance than public geth ## Architecture -![Quorum privacy architecture](https://github.com/jpmorganchase/quorum-docs/raw/master/images/QuorumTransactionProcessing.JPG) +![Quorum Tessera Privacy Flow](https://raw.githubusercontent.com/jpmorganchase/quorum-docs/master/images/QuorumTransactionProcessing.JPG) -The above diagram is a high-level overview of the privacy architecture used by Quorum. For more in-depth discussion of the components, refer to the [wiki](https://github.com/jpmorganchase/quorum/wiki/) pages. +The above diagram is a high-level overview of the privacy architecture used by Quorum. For more in-depth discussion of the components, refer to the [wiki](https://github.com/jpmorganchase/quorum/wiki) pages. ## Quickstart - -The quickest way to get started with Quorum is using [VirtualBox](https://www.virtualbox.org/wiki/Downloads) and [Vagrant](https://www.vagrantup.com/downloads.html): - -```sh -git clone https://github.com/jpmorganchase/quorum-examples -cd quorum-examples -vagrant up -# (should take 5 or so minutes) -vagrant ssh -``` - -Now that you have a fully-functioning Quorum environment set up, let's run the 7-node cluster example. This will spin up several nodes with a mix of voters, block makers, and unprivileged nodes. - -```sh -# (from within vagrant env, use `vagrant ssh` to enter) -ubuntu@ubuntu-xenial:~$ cd quorum-examples/7nodes - -$ ./raft-init.sh -# (output condensed for clarity) -[*] Cleaning up temporary data directories -[*] Configuring node 1 -[*] Configuring node 2 as block maker and voter -[*] Configuring node 3 -[*] Configuring node 4 as voter -[*] Configuring node 5 as voter -[*] Configuring node 6 -[*] Configuring node 7 - -$ ./raft-start.sh -[*] Starting Constellation nodes -[*] Starting bootnode... waiting... done -[*] Starting node 1 -[*] Starting node 2 -[*] Starting node 3 -[*] Starting node 4 -[*] Starting node 5 -[*] Starting node 6 -[*] Starting node 7 -[*] Unlocking account and sending first transaction -Contract transaction send: TransactionHash: 0xbfb7bfb97ba9bacbf768e67ac8ef05e4ac6960fc1eeb6ab38247db91448b8ec6 waiting to be mined... -true -``` - -We now have a 7-node Quorum cluster with a [private smart contract](https://github.com/jpmorganchase/quorum-examples/blob/master/examples/7nodes/script1.js) (SimpleStorage) sent from `node 1` "for" `node 7` (denoted by the public key passed via `privateFor: ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]` in the `sendTransaction` call). - -Connect to any of the nodes and inspect them using the following commands: - -```sh -$ geth attach ipc:qdata/dd1/geth.ipc -$ geth attach ipc:qdata/dd2/geth.ipc -... -$ geth attach ipc:qdata/dd7/geth.ipc - - -# e.g. - -$ geth attach ipc:qdata/dd2/geth.ipc -Welcome to the Geth JavaScript console! - -instance: Geth/v1.5.0-unstable/linux/go1.7.3 -coinbase: 0xca843569e3427144cead5e4d5999a3d0ccf92b8e -at block: 679 (Tue, 15 Nov 2016 00:01:05 UTC) - datadir: /home/ubuntu/quorum-examples/7nodes/qdata/dd2 - modules: admin:1.0 debug:1.0 eth:1.0 net:1.0 personal:1.0 quorum:1.0 rpc:1.0 txpool:1.0 web3:1.0 - -# let's look at the private txn created earlier: -> eth.getTransaction("0xbfb7bfb97ba9bacbf768e67ac8ef05e4ac6960fc1eeb6ab38247db91448b8ec6") -{ - blockHash: "0xb6aec633ef1f79daddc071bec8a56b7099ab08ac9ff2dc2764ffb34d5a8d15f8", - blockNumber: 1, - from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d", - gas: 300000, - gasPrice: 0, - hash: "0xbfb7bfb97ba9bacbf768e67ac8ef05e4ac6960fc1eeb6ab38247db91448b8ec6", - input: "0x9820c1a5869713757565daede6fcec57f3a6b45d659e59e72c98c531dcba9ed206fd0012c75ce72dc8b48cd079ac08536d3214b1a4043da8cea85be858b39c1d", - nonce: 0, - r: "0x226615349dc143a26852d91d2dff1e57b4259b576f675b06173e9972850089e7", - s: "0x45d74765c5400c5c280dd6285a84032bdcb1de85a846e87b57e9e0cedad6c427", - to: null, - transactionIndex: 1, - v: "0x25", - value: 0 -} -``` - -Note in particular the `v` field value of "0x25" or "0x26" (37 or 38 in decimal) which marks this transaction as having a private payload (input). - -## Demonstrating Privacy -Documentation detailing steps to demonstrate the privacy features of Quorum can be found in [quorum-examples/7nodes/README](https://github.com/jpmorganchase/quorum-examples/tree/master/examples/7nodes/README.md). +The quickest way to get started with Quorum is by following instructions in the [Quorum Examples](https://github.com/jpmorganchase/quorum-examples) repository. This allows you to quickly create a network of Quorum nodes, and includes a step-by-step demonstration of the privacy features of Quorum. ## Further Reading -Further documentation can be found in the [docs](docs/) folder and on the [wiki](https://github.com/jpmorganchase/quorum/wiki/). +Further documentation can be found in the [docs](docs/) folder and on the [wiki](https://github.com/jpmorganchase/quorum/wiki). ## See also * [Quorum](https://github.com/jpmorganchase/quorum): this repository -* [Constellation](https://github.com/jpmorganchase/constellation): peer-to-peer encrypted message exchange for transaction privacy +* [Quorum Wiki](https://github.com/jpmorganchase/quorum/wiki) +* [quorum-examples](https://github.com/jpmorganchase/quorum-examples): Quorum demonstration examples +* [Quorum Community Slack Inviter](https://clh7rniov2.execute-api.us-east-1.amazonaws.com/Express/): Quorum Slack community entry point +* [Constellation](https://github.com/jpmorganchase/constellation): Haskell implementation of peer-to-peer encrypted message exchange for transaction privacy +* [Tessera](https://github.com/jpmorganchase/tessera): Java implementation of peer-to-peer encrypted message exchange for transaction privacy * [Raft Consensus Documentation](raft/doc.md) * [Istanbul BFT Consensus Documentation](https://github.com/ethereum/EIPs/issues/650): [RPC API](https://github.com/getamis/go-ethereum/wiki/RPC-API) and [technical article](https://medium.com/getamis/istanbul-bft-ibft-c2758b7fe6ff) * [ZSL](https://github.com/jpmorganchase/quorum/wiki/ZSL) wiki page and [documentation](https://github.com/jpmorganchase/zsl-q/blob/master/README.md) -* [quorum-examples](https://github.com/jpmorganchase/quorum-examples): example quorum clusters * [quorum-tools](https://github.com/jpmorganchase/quorum-tools): local cluster orchestration, and integration testing tool -* [Quorum Wiki](https://github.com/jpmorganchase/quorum/wiki) -* [Quorum Community Slack Inviter](https://clh7rniov2.execute-api.us-east-1.amazonaws.com/Express/): Quorum Slack community entry point ## Third Party Tools/Libraries @@ -141,6 +52,8 @@ The following Quorum-related libraries/applications have been created by Third P * [ERC20 REST service](https://github.com/blk-io/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/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 + ## Contributing diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index a00ae00c1..80a969ae0 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -40,6 +40,7 @@ var customGenesisTests = []struct { "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00" + "config" : {"isQuorum":false} }`, query: "eth.getBlock(0).nonce", result: "0x0000000000000042", @@ -56,7 +57,7 @@ var customGenesisTests = []struct { "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", - "config" : {} + "config" : {"isQuorum":false } }`, query: "eth.getBlock(0).nonce", result: "0x0000000000000042", @@ -76,7 +77,9 @@ var customGenesisTests = []struct { "config" : { "homesteadBlock" : 314, "daoForkBlock" : 141, - "daoForkSupport" : true + "daoForkSupport" : true, + "isQuorum" : false + }, }`, query: "eth.getBlock(0).nonce", diff --git a/core/database_util.go b/core/database_util.go index 543789640..306b76789 100644 --- a/core/database_util.go +++ b/core/database_util.go @@ -77,6 +77,8 @@ var ( privateblockReceiptsPrefix = []byte("Pr") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts privateReceiptPrefix = []byte("Prs") privateBloomPrefix = []byte("Pb") + + quorumEIP155ActivatedPrefix = []byte("quorum155active") ) // txLookupEntry is a positional metadata to help looking up the data content of @@ -117,6 +119,13 @@ func GetBlockNumber(db DatabaseReader, hash common.Hash) uint64 { return binary.BigEndian.Uint64(data) } +//returns whether we have a chain configuration that can't be updated +//after the EIP155 HF has happened +func GetIsQuorumEIP155Activated(db DatabaseReader) bool { + data, _ := db.Get(quorumEIP155ActivatedPrefix) + return len(data) == 1 +} + // GetHeadHeaderHash retrieves the hash of the current canonical head block's // header. The difference between this and GetHeadBlockHash is that whereas the // last block hash is only updated upon a full block import, the last header @@ -595,6 +604,11 @@ func WriteChainConfig(db ethdb.Putter, hash common.Hash, cfg *params.ChainConfig return db.Put(append(configPrefix, hash[:]...), jsonChainConfig) } +// WriteQuorumEIP155Activation writes a flag to the database saying EIP155 HF is enforced +func WriteQuorumEIP155Activation(db ethdb.Putter) error { + return db.Put(quorumEIP155ActivatedPrefix, []byte{1}) +} + // GetChainConfig will fetch the network settings based on the given hash. func GetChainConfig(db DatabaseReader, hash common.Hash) (*params.ChainConfig, error) { jsonChainConfig, _ := db.Get(append(configPrefix, hash[:]...)) diff --git a/core/database_util_test.go b/core/database_util_test.go index 36f43cf50..fe33aea42 100644 --- a/core/database_util_test.go +++ b/core/database_util_test.go @@ -386,3 +386,22 @@ func TestBlockReceiptStorage(t *testing.T) { t.Fatalf("deleted receipts returned: %v", rs) } } + +// Tests that setting the flag for Quorum EIP155 activation read values correctly +func TestIsQuorumEIP155Active(t *testing.T) { + db, _ := ethdb.NewMemDatabase() + + isQuorumEIP155Active := GetIsQuorumEIP155Activated(db) + if isQuorumEIP155Active { + t.Fatal("Quorum EIP155 active read to be set, but wasn't set beforehand") + } + + dbSet, _ := ethdb.NewMemDatabase() + WriteQuorumEIP155Activation(dbSet) + + isQuorumEIP155ActiveAfterSetting := GetIsQuorumEIP155Activated(dbSet) + if !isQuorumEIP155ActiveAfterSetting { + t.Fatal("Quorum EIP155 active read to be unset, but was set beforehand") + } +} + diff --git a/core/genesis.go b/core/genesis.go index d5e39ace4..ce304ebea 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -200,7 +200,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig if height == missingNumber { return newcfg, stored, fmt.Errorf("missing block number for head header hash") } - compatErr := storedcfg.CheckCompatible(newcfg, height) + compatErr := storedcfg.CheckCompatible(newcfg, height, GetIsQuorumEIP155Activated(db)) if compatErr != nil && height != 0 && compatErr.RewindTo != 0 { return newcfg, stored, compatErr } diff --git a/core/genesis_test.go b/core/genesis_test.go index 47bfbad35..417dd46e0 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -23,8 +23,8 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core/vm" + // "github.com/ethereum/go-ethereum/consensus/ethash" + // "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" ) @@ -42,7 +42,7 @@ func TestDefaultGenesisBlock(t *testing.T) { func TestSetupGenesis(t *testing.T) { var ( - customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50") + // customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50") customg = Genesis{ Config: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3)}, Alloc: GenesisAlloc{ @@ -84,57 +84,57 @@ func TestSetupGenesis(t *testing.T) { wantHash: params.MainnetGenesisHash, 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: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3), IsQuorum: true}, - }, - { - name: "custom block in DB, genesis == testnet", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - customg.MustCommit(db) - return SetupGenesisBlock(db, DefaultTestnetGenesisBlock()) - }, - wantErr: &GenesisMismatchError{Stored: customghash, New: params.TestnetGenesisHash}, - wantHash: params.TestnetGenesisHash, - wantConfig: params.TestnetChainConfig, - }, - { - name: "compatible config in DB", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - oldcustomg.MustCommit(db) - return SetupGenesisBlock(db, &customg) - }, - wantHash: customghash, - wantConfig: customg.Config, - }, - { - name: "incompatible config in DB", - 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, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}) - defer bc.Stop() - bc.SetValidator(bproc{}) - bc.InsertChain(makeBlockChainWithDiff(genesis, []int{2, 3, 4, 5}, 0)) - bc.CurrentBlock() - // This should return a compatibility error. - return SetupGenesisBlock(db, &customg) - }, - wantHash: customghash, - wantConfig: customg.Config, - wantErr: ¶ms.ConfigCompatError{ - What: "Homestead fork block", - StoredConfig: big.NewInt(2), - NewConfig: big.NewInt(3), - RewindTo: 1, - }, - }, + // { + // 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: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3), IsQuorum: true}, + // // }, + // { + // name: "custom block in DB, genesis == testnet", + // fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + // customg.MustCommit(db) + // return SetupGenesisBlock(db, DefaultTestnetGenesisBlock()) + // }, + // wantErr: &GenesisMismatchError{Stored: customghash, New: params.TestnetGenesisHash}, + // wantHash: params.TestnetGenesisHash, + // wantConfig: params.TestnetChainConfig, + // }, + // { + // name: "compatible config in DB", + // fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + // oldcustomg.MustCommit(db) + // return SetupGenesisBlock(db, &customg) + // }, + // wantHash: customghash, + // wantConfig: customg.Config, + // }, + // { + // name: "incompatible config in DB", + // 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, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}) + // defer bc.Stop() + // bc.SetValidator(bproc{}) + // bc.InsertChain(makeBlockChainWithDiff(genesis, []int{2, 3, 4, 5}, 0)) + // bc.CurrentBlock() + // // This should return a compatibility error. + // return SetupGenesisBlock(db, &customg) + // }, + // wantHash: customghash, + // wantConfig: customg.Config, + // wantErr: ¶ms.ConfigCompatError{ + // What: "Homestead fork block", + // StoredConfig: big.NewInt(2), + // NewConfig: big.NewInt(3), + // RewindTo: 1, + // }, + // }, } for _, test := range tests { diff --git a/docs/Quorum Whitepaper v0.2.pdf b/docs/Quorum Whitepaper v0.2.pdf new file mode 100644 index 000000000..c2dac8f0b Binary files /dev/null and b/docs/Quorum Whitepaper v0.2.pdf differ diff --git a/docs/README.md b/docs/README.md index c58d70126..cbddd4a82 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,8 @@ # Quorum documentation -* [Whitepaper](https://github.com/jpmorganchase/quorum-docs/raw/master/Quorum%20Whitepaper%20v0.1.pdf) (PDF) - Quorum Whitepaper [demo video](https://vimeo.com/user5833792/review/210456842/a42d0fcb87) +* [Whitepaper](./Quorum%20Whitepaper%20v0.2.pdf) (PDF) - Quorum Whitepaper [demo video](https://vimeo.com/user5833792/review/210456842/a42d0fcb87) * [Design](./design.md) - Quorum design overview * [Privacy](./privacy.md) - Sending private transactions [privacy video](https://vimeo.com/user5833792/review/210456729/8f70cfaaa5) -* [Running](./running.md) - Detailed instructions for running Quorum nodes (see also [Constellation](https://github.com/jpmorganchase/constellation)) +* [Running](./running.md) - Detailed instructions for running Quorum nodes (see also [Constellation](https://github.com/jpmorganchase/constellation), [Tessera](https://github.com/jpmorganchase/tessera)) * [API](./api.md) - new privacy API diff --git a/docs/api.md b/docs/api.md index 1eb120a60..a60a08cfd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3,7 +3,7 @@ ## Privacy APIs -### `web3.eth.sendTransaction(object)` was modified to support private transactions +__To support private transactions in Quorum, the `web3.eth.sendTransaction(object)` API method has been modified.__ ```js web3.eth.sendTransaction(transactionObject [, callback]) @@ -14,22 +14,22 @@ Sends a transaction to the network. ##### Parameters 1. `Object` - The transaction object to send: - - `from`: `String` - The address for the sending account. Uses the [web3.eth.defaultAccount](#web3ethdefaultaccount) property, if not specified. - - `to`: `String` - (optional) The destination address of the message, left undefined for a contract-creation transaction. - - `value`: `Number|String|BigNumber` - (optional) The value transferred for the transaction in Wei, also the endowment if it's a contract-creation transaction. - - `gas`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The amount of gas to use for the transaction (unused gas is refunded). - - `gasPrice`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The price of gas for this transaction in wei, defaults to the mean network gas price. - - `data`: `String` - (optional) Either a [byte string](https://github.com/ethereum/wiki/wiki/Solidity,-Docs-and-ABI) containing the associated data of the message, or in the case of a contract-creation transaction, the initialisation code. - - `nonce`: `Number` - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce. - - `privateFrom`: `String` - (optional) When sending a private transaction, the sending party's base64-encoded public key to use. If not present *and* passing `privateFor`, use the default key as configured in the `TransactionManager`. - - `privateFor`: `List` - (optional) When sending a private transaction, an array of the recipients' base64-encoded public keys. -2. `Function` - (optional) If you pass a callback the HTTP request is made asynchronous. See [this note](#using-callbacks) for details. + - `from`: `String` - The address for the sending account. Uses the `web3.eth.defaultAccount` property, if not specified. + - `to`: `String` - (optional) The destination address of the message, left undefined for a contract-creation transaction. + - `value`: `Number|String|BigNumber` - (optional) The value transferred for the transaction in Wei, also the endowment if it's a contract-creation transaction. + - `gas`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The amount of gas to use for the transaction (unused gas is refunded). + - `gasPrice`: `Number|String|BigNumber` - (optional, default: To-Be-Determined) The price of gas for this transaction in wei, defaults to the mean network gas price. + - `data`: `String` - (optional) Either a [byte string](https://github.com/ethereum/wiki/wiki/Solidity,-Docs-and-ABI) containing the associated data of the message, or in the case of a contract-creation transaction, the initialisation code. + - `nonce`: `Number` - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce. + - `privateFrom`: `String` - (optional) When sending a private transaction, the sending party's base64-encoded public key to use. If not present *and* passing `privateFor`, use the default key as configured in the `TransactionManager`. + - `privateFor`: `List` - (optional) When sending a private transaction, an array of the recipients' base64-encoded public keys. +2. `Function` - (optional) If you pass a callback the HTTP request is made asynchronous. ##### Returns `String` - The 32 Bytes transaction hash as HEX string. -If the transaction was a contract creation use [web3.eth.getTransactionReceipt()](#web3gettransactionreceipt) to get the contract address, after the transaction was mined. +If the transaction was a contract creation use `web3.eth.getTransactionReceipt()` to get the contract address, after the transaction was mined. ##### Example diff --git a/docs/design.md b/docs/design.md index 0fb92d2df..a9abe8e9d 100644 --- a/docs/design.md +++ b/docs/design.md @@ -5,15 +5,15 @@ Quorum supports dual state: -- public state, accessible by all nodes within the network -- private state, only accessible by nodes with the correct permissions +- Public state: accessible by all nodes within the network +- Private state: only accessible by nodes with the correct permissions The difference is made through the use of transactions with encrypted (private) and non-encrypted payloads (public). Nodes can determine if a transaction is private by looking at the `v` value of the signature. -Public transactions have a `v` value of 27 or 28, private transactions have a value of 37 or 38. +Public transactions have a `v` value of `27` or `28`, private transactions have a value of `37` or `38`. -If the transaction is private and the node has the ability to decrypt the payload it can execute the transaction. -Nodes who are not involved in the transaction cannot decrypt the payload and process the transaction. +If the transaction is private, the node can only execute the transaction if it has the ability to decrypt the payload. +Nodes who are not involved in the transaction cannot decrypt the payload and are therefore unable to process the transaction. As a result all nodes share a common public state which is created through public transactions and have a local unique private state. This model imposes a restriction in the ability to modify state in private transactions. @@ -23,23 +23,31 @@ If the virtual machine is in read only mode and the code tries to make a state c The following transactions are allowed: -S: sender, (X): private, X: public, ->: direction, []: read only mode ``` 1. S -> A -> B 2. S -> (A) -> (B) 3. S -> (A) -> [B -> C] ``` -The following transaction are unsupported: + +and the following transaction are unsupported: ``` 1. (S) -> A 2. (S) -> (A) ``` +where: +- `S` = sender +- `(X)` = private +- `X` = public +- `->` = direction +- `[]` = read only mode + ### State verification To determine if nodes are in sync the public state root hash is included in the block. Since private transactions can only be processed by nodes that are involved its impossible to get global consensus on the private state. + To overcome this issue the RPC method `eth_storageRoot(address[, blockNumber]) -> hash` can be used. It returns the storage root for the given address at an (optional) block number. If the optional block number is not given the latest block number is used. diff --git a/docs/privacy.md b/docs/privacy.md index 31e21a30f..cd04f334d 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -3,22 +3,17 @@ ## Sending Private Transactions -To send a private transaction, a `PrivateTransactionManager` must be configured. This is the +To send a private transaction, a private Transaction Manager must be configured. This is the service which transfers private payloads to their intended recipients, performing encryption and related operations in the process. -Currently, `constellation` is supported out of the box via the `PRIVATE_CONFIG` environment -variable (please note that this integration method will change in the near future.) See the -`7nodes` folder in the `quorum-examples` repository for a complete example of how to use it. -The transaction sent in `script1.js` is private for node 7's `PrivateTransactionManager` -public key. +[Constellation](https://github.com/jpmorganchase/constellation) / [Tessera](https://github.com/jpmorganchase/tessera) is used to provide the private Transaction Manager for a Quorum node. Once a Constellation/Tessera node is running, the `PRIVATE_CONFIG` environment variable is used to point the Quorum node to the transaction manager instance. Examples of this can be seen in the [quorum-examples 7nodes](https://github.com/jpmorganchase/quorum-examples) source files. -Once `constellation` is launched and `PRIVATE_CONFIG` points to a valid configuration file, +Once Constellation/Tessera is launched and `PRIVATE_CONFIG` points to a valid configuration file, a `SendTransaction` call can be made private by specifying the `privateFor` argument. -`privateFor` is a list of public keys of the intended recipients. (Note that in the case of -`constellation`, this public key is distinct from Ethereum account keys.) When a transaction +`privateFor` is a list of public keys of the intended recipients (these public keys are distinct from Ethereum account keys). When a transaction is private, the transaction contents will be sent to the `PrivateTransactionManager` and the identifier returned will be placed in the transaction instead. When other Quorum nodes receive a private transaction, they will query their `PrivateTransactionManager` for the -identifier and replace the transaction contents with the result (if any; nodes which are -not party to a transaction will not be able to retrieve the original contents.) +identifier and replace the transaction contents with the result. Nodes which are +not party to a transaction will not be able to retrieve the original contents. \ No newline at end of file diff --git a/docs/running.md b/docs/running.md index 9b5adf3d9..ce3f6ebba 100644 --- a/docs/running.md +++ b/docs/running.md @@ -1,7 +1,7 @@ # Running Quorum -A `--permissioned` CLI argument was introduced with Quorum. +Quorum introduces the `--permissioned` CLI argument: ``` QUORUM OPTIONS: @@ -53,15 +53,15 @@ Optionally you can set up a bootnode that all the other nodes will first connect 1. To generate the key for the first time: -`bootnode -genkey tmp_file.txt // this will start a bootnode with an enode address and generate a key inside a “tmp_file.txt” file` + `bootnode -genkey tmp_file.txt // this will start a bootnode with an enode address and generate a key inside a “tmp_file.txt” file` 2. To later restart the bootnode using the same key (and hence use the same enode url): -`bootnode -nodekey tmp_file.txt` + `bootnode -nodekey tmp_file.txt` -or + or -`bootnode -nodekeyhex 77bd02ffa26e3fb8f324bda24ae588066f1873d95680104de5bc2db9e7b2e510 // Key from tmp_file.txt` + `bootnode -nodekeyhex 77bd02ffa26e3fb8f324bda24ae588066f1873d95680104de5bc2db9e7b2e510 // Key from tmp_file.txt` ### Start node @@ -72,34 +72,36 @@ Starting a node is as simple as `geth`. This will start the node without any of ## Setup multi-node network -Quorum comes with several scripts to setup a private test network with 7 nodes in the `7nodes` folder in the `quorum-examples` repository. - -1. Step 1, run `raft-init.sh` and initialize data directories (change variables accordingly) -2. Step 2, start nodes with `raft-start.sh` (change variables accordingly) -3. Step 3, stop network with `stop.sh` +The [quorum-examples 7nodes](https://github.com/jpmorganchase/quorum-examples) source files contain several scripts demonstrating how to set up a private test network made up of 7 nodes. ## Permissioned Network -Node Permissioning is a feature that controls which nodes can connect to a given node and also to which nodes this node can dial out to. Currently, it is managed at individual node level by the command line flag `--permissioned` while starting the node. +Node Permissioning is a feature of Quorum that is used to define: +1. The nodes that a particular Quorum node is able to connect to +2. The nodes that a particular Quorum node is able to receive connections from -If the `--permissioned` node is present, the node looks for a file named `/permissioned-nodes.json`. This file contains the list of enodes that this node can connect to and also accepts connections only from those nodes. In other words, if permissioning is enabled, only the nodes that are listed in this file become part of the network. It is an error to enable `--permissioned` but not have the `permissioned-nodes.json` file. If the flag is given, but no nodes are present in this file, then this node can neither connect to any node or accept any incoming connections. +Permissioning is managed at the individual node level by using the `--permissioned` command line flag when starting the node. -The `permissioned-nodes.json` follows following pattern (similar to `static-nodes.json`): +If a node is started with `--permissioned` set, the node will look for a `/permissioned-nodes.json` file. This file contains the list of enodes that this node can connect to and accept connections from. In other words, if permissioning is enabled, only the nodes that are listed in the `permissioned-nodes.json` file become part of the network. + +If `--permissioned` is set, a `permissioned-nodes.json` file must be provided. If the flag is set but no nodes are present in this file, then the node will be unable to make any outward connections or accept any incoming connections. + +The format of `permissioned-nodes.json` is similar to `static-nodes.json`: ```json [ "enode://enodehash1@ip1:port1", "enode://enodehash2@ip2:port2", - "enode://enodehash3@ip3:port3", + "enode://enodehash3@ip3:port3" ] ``` -Sample file: +For example, including the hash, a sample file might look like: ```json [ - "enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300", + "enode://6598638ac5b15ee386210156a43f565fa8c48592489d3e66ac774eac759db9eb52866898cf0c5e597a1595d9e60e1a19c84f77df489324e2f3a967207c047470@127.0.0.1:30300" ] ``` -In the current release, every node has its own copy of `permissioned-nodes.json`. In a future release, the permissioned nodes list will be moved to a smart contract, thereby keeping the list on chain and one global list of nodes that connect to the network. +In the current release, every node has its own copy of `permissioned-nodes.json`. In a future release, the permissioned nodes list will be moved to a smart contract, thereby keeping the list on-chain and requiring just one global list of nodes that connect to the network. diff --git a/eth/backend.go b/eth/backend.go index 3e970b28b..6fff7c892 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -125,6 +125,19 @@ 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 !core.GetIsQuorumEIP155Activated(chainDb) && chainConfig.ChainId != nil { + //Upon starting the node, write the flag to disallow changing ChainID/EIP155 block after HF + core.WriteQuorumEIP155Activation(chainDb) + } + eth := &Ethereum{ config: config, chainDb: chainDb, diff --git a/eth/config.go b/eth/config.go index 197d4c0c6..3a50542ea 100644 --- a/eth/config.go +++ b/eth/config.go @@ -40,7 +40,7 @@ var DefaultConfig = Config{ EthashCachesOnDisk: 3, EthashDatasetsInMem: 1, EthashDatasetsOnDisk: 2, - NetworkId: 1, + NetworkId: 1337, LightPeers: 20, DatabaseCache: 128, GasPrice: big.NewInt(18 * params.Shannon), diff --git a/logo.png b/logo.png new file mode 100644 index 000000000..28de50d44 Binary files /dev/null and b/logo.png differ diff --git a/params/config.go b/params/config.go index 9af86d89f..1db4e18f3 100644 --- a/params/config.go +++ b/params/config.go @@ -248,13 +248,13 @@ func (c *ChainConfig) GasTable(num *big.Int) GasTable { // 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) // 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 } @@ -264,7 +264,7 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *Confi return lasterr } -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) } @@ -277,15 +277,15 @@ 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) } - if c.IsEIP158(head) && !configNumEqual(c.ChainId, newcfg.ChainId) { - return newCompatError("EIP158 chain ID", c.EIP158Block, newcfg.EIP158Block) - } if isForkIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, head) { return newCompatError("Byzantium fork block", c.ByzantiumBlock, newcfg.ByzantiumBlock) } diff --git a/params/config_test.go b/params/config_test.go index 02c5fe291..61a8bbab7 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -73,7 +73,7 @@ func TestCheckCompatible(t *testing.T) { } 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/version.go b/params/version.go index c850697d5..1b0306019 100644 --- a/params/version.go +++ b/params/version.go @@ -27,8 +27,8 @@ const ( VersionMeta = "stable" // Version metadata to append to the version string QuorumVersionMajor = 2 - QuorumVersionMinor = 0 - QuorumVersionPatch = 2 + QuorumVersionMinor = 1 + QuorumVersionPatch = 0 ) // Version holds the textual version string. diff --git a/private/constellation/node.go b/private/constellation/node.go index 9a97ae26b..ce4a420eb 100644 --- a/private/constellation/node.go +++ b/private/constellation/node.go @@ -92,10 +92,17 @@ func (c *Client) SendPayload(pl []byte, b64From string, b64To []string) ([]byte, req.Header.Set("c11n-to", strings.Join(b64To, ",")) req.Header.Set("Content-Type", "application/octet-stream") res, err := c.httpClient.Do(req) - if err == nil && res.StatusCode != 200 { + + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + if res.StatusCode != 200 { return nil, fmt.Errorf("Non-200 status code: %+v", res) } - defer res.Body.Close() + return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body)) } @@ -106,10 +113,17 @@ func (c *Client) ReceivePayload(key []byte) ([]byte, error) { } req.Header.Set("c11n-key", base64.StdEncoding.EncodeToString(key)) res, err := c.httpClient.Do(req) - if err == nil && res.StatusCode != 200 { + + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + if res.StatusCode != 200 { return nil, fmt.Errorf("Non-200 status code: %+v", res) } - defer res.Body.Close() + return ioutil.ReadAll(res.Body) }