Merge remote-tracking branch 'remotes/origin/master' into geth-upgrade-1.8.12

# Conflicts:
#	.travis.yml
#	README.md
#	cmd/geth/genesis_test.go
#	core/genesis.go
#	core/genesis_test.go
#	core/state_processor.go
#	eth/config.go
#	eth/downloader/downloader_test.go
#	eth/protocol_test.go
#	params/config.go
This commit is contained in:
amalraj.manigmail.com 2018-09-27 18:55:55 +08:00
commit 72270b10c7
35 changed files with 543 additions and 293 deletions

View File

@ -1,3 +1,5 @@
# simplifed version of the upstream travis configuration
language: go
go_import_path: github.com/ethereum/go-ethereum
sudo: false

View File

@ -4,13 +4,15 @@ FROM golang:1.10-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers
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 30303 30303/udp
ENTRYPOINT ["geth"]

View File

@ -16,6 +16,10 @@ geth:
@echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth."
bootnode:
build/env.sh go run build/ci.go install ./cmd/bootnode
@echo "Done building bootnode."
swarm:
build/env.sh go run build/ci.go install ./cmd/swarm
@echo "Done building."

142
README.md
View File

@ -1,6 +1,7 @@
# Quorum
# <img src="logo.png" width="100" height="100"/>
<a href="https://clh7rniov2.execute-api.us-east-1.amazonaws.com/Express/" target="_blank" rel="noopener"><img title="Quorum Slack" src="https://clh7rniov2.execute-api.us-east-1.amazonaws.com/Express/badge.svg" alt="Quorum Slack" /></a>
[![Build Status](https://travis-ci.org/jpmorganchase/quorum.svg?branch=master)](https://travis-ci.org/jpmorganchase/quorum)
Quorum is an Ethereum-based distributed ledger protocol with transaction/contract privacy and new consensus mechanisms.
@ -8,142 +9,41 @@ 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
<a href="https://github.com/jpmorganchase/quorum/wiki/Transaction-Processing#private-transaction-process-flow">![Quorum privacy architecture](https://github.com/jpmorganchase/quorum-docs/raw/master/images/QuorumTransactionProcessing.JPG)</a>
![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
```
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 `--rpcaddr 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.
### Programatically interfacing Geth nodes
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).
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).
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
* [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-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
* Quorum Transaction Managers
* [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
* Quorum supported consensuses
* [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)
* [Clique POA Consensus Documentation](https://github.com/ethereum/EIPs/issues/225) and a [guide to setup clique json](https://modalduality.org/posts/puppeth/) with [puppeth](https://blog.ethereum.org/2017/04/14/geth-1-6-puppeth-master/)
* [ZSL](https://github.com/jpmorganchase/quorum/wiki/ZSL) wiki page and [documentation](https://github.com/jpmorganchase/zsl-q/blob/master/README.md)
* [quorum-tools](https://github.com/jpmorganchase/quorum-tools): local cluster orchestration, and integration testing tool
## Third Party Tools/Libraries
@ -156,6 +56,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

View File

@ -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

View File

@ -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,8 +77,10 @@ var customGenesisTests = []struct {
"config" : {
"homesteadBlock" : 314,
"daoForkBlock" : 141,
"daoForkSupport" : true
}
"daoForkSupport" : true,
"isQuorum" : false
},
}`,
query: "eth.getBlock(0).nonce",
result: "0x0000000000000042",

View File

@ -88,7 +88,8 @@ func (c *core) storeBacklog(msg *message, src istanbul.Validator) {
c.backlogsMu.Lock()
defer c.backlogsMu.Unlock()
backlog := c.backlogs[src]
logger.Debug("Retrieving backlog queue", "for", src.Address(), "backlogs_size", len(c.backlogs))
backlog := c.backlogs[src.Address()]
if backlog == nil {
backlog = prque.New()
}
@ -107,18 +108,23 @@ func (c *core) storeBacklog(msg *message, src istanbul.Validator) {
backlog.Push(msg, toPriority(msg.Code, p.View))
}
}
c.backlogs[src] = backlog
c.backlogs[src.Address()] = backlog
}
func (c *core) processBacklog() {
c.backlogsMu.Lock()
defer c.backlogsMu.Unlock()
for src, backlog := range c.backlogs {
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

View File

@ -25,7 +25,6 @@ import (
"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/event"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
@ -168,14 +167,15 @@ func TestCheckMessage(t *testing.T) {
func TestStoreBacklog(t *testing.T) {
c := &core{
logger: log.New("backend", "test", "id", 0),
backlogs: make(map[istanbul.Validator]*prque.Prque),
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 := validator.New(common.StringToAddress("12345667890"))
p := c.valSet.GetByIndex(0)
// push preprepare msg
preprepare := &istanbul.Preprepare{
View: v,
@ -187,7 +187,7 @@ func TestStoreBacklog(t *testing.T) {
Msg: prepreparePayload,
}
c.storeBacklog(m, p)
msg := c.backlogs[p].PopItem()
msg := c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
@ -204,7 +204,7 @@ func TestStoreBacklog(t *testing.T) {
Msg: subjectPayload,
}
c.storeBacklog(m, p)
msg = c.backlogs[p].PopItem()
msg = c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
@ -215,7 +215,7 @@ func TestStoreBacklog(t *testing.T) {
Msg: subjectPayload,
}
c.storeBacklog(m, p)
msg = c.backlogs[p].PopItem()
msg = c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
@ -226,7 +226,7 @@ func TestStoreBacklog(t *testing.T) {
Msg: subjectPayload,
}
c.storeBacklog(m, p)
msg = c.backlogs[p].PopItem()
msg = c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
@ -238,7 +238,8 @@ func TestProcessFutureBacklog(t *testing.T) {
}
c := &core{
logger: log.New("backend", "test", "id", 0),
backlogs: make(map[istanbul.Validator]*prque.Prque),
valSet: newTestValidatorSet(1),
backlogs: make(map[common.Address]*prque.Prque),
backlogsMu: new(sync.Mutex),
backend: backend,
current: newRoundState(&istanbul.View{
@ -254,7 +255,7 @@ func TestProcessFutureBacklog(t *testing.T) {
Round: big.NewInt(10),
Sequence: big.NewInt(10),
}
p := validator.New(common.StringToAddress("12345667890"))
p := c.valSet.GetByIndex(0)
// push a future msg
subject := &istanbul.Subject{
View: v,
@ -329,8 +330,9 @@ func testProcessBacklog(t *testing.T, msg *message) {
}
c := &core{
logger: log.New("backend", "test", "id", 0),
backlogs: make(map[istanbul.Validator]*prque.Prque),
backlogs: make(map[common.Address]*prque.Prque),
backlogsMu: new(sync.Mutex),
valSet: vset,
backend: backend,
state: State(msg.Code),
current: newRoundState(&istanbul.View{

View File

@ -42,7 +42,7 @@ func New(backend istanbul.Backend, config *istanbul.Config) Engine {
handlerWg: new(sync.WaitGroup),
logger: log.New("address", backend.Address()),
backend: backend,
backlogs: make(map[istanbul.Validator]*prque.Prque),
backlogs: make(map[common.Address]*prque.Prque),
backlogsMu: new(sync.Mutex),
pendingRequests: prque.New(),
pendingRequestsMu: new(sync.Mutex),
@ -78,7 +78,7 @@ type core struct {
waitingForRoundChange bool
validateFn func([]byte, []byte) (common.Address, error)
backlogs map[istanbul.Validator]*prque.Prque
backlogs map[common.Address]*prque.Prque
backlogsMu *sync.Mutex
current *roundState

View File

@ -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[:]...))

View File

@ -197,7 +197,7 @@ 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)
compatErr := storedcfg.CheckCompatible(newcfg, *height, GetIsQuorumEIP155Activated(db))
if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 {
return newcfg, stored, compatErr
}

View File

@ -23,9 +23,9 @@ 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/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"
)
@ -43,7 +43,7 @@ func TestDefaultGenesisBlock(t *testing.T) {
func TestSetupGenesis(t *testing.T) {
var (
customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50")
// customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50")
customg = Genesis{
Config: &params.ChainConfig{HomesteadBlock: big.NewInt(3), IsQuorum: true},
Alloc: GenesisAlloc{
@ -85,59 +85,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: &params.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, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{})
defer bc.Stop()
blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil)
bc.InsertChain(blocks)
bc.CurrentBlock()
// This should return a compatibility error.
return SetupGenesisBlock(db, &customg)
},
wantHash: customghash,
wantConfig: customg.Config,
wantErr: &params.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: &params.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: &params.ConfigCompatError{
// What: "Homestead fork block",
// StoredConfig: big.NewInt(2),
// NewConfig: big.NewInt(3),
// RewindTo: 1,
// },
// },
}
for _, test := range tests {

View File

@ -2,13 +2,18 @@ package core
import (
"bytes"
"errors"
"fmt"
"html/template"
"io/ioutil"
"math/big"
"net"
"net/http"
"os"
osExec "os/exec"
"path"
"path/filepath"
"strings"
"testing"
"time"
@ -127,6 +132,136 @@ func runConstellation() (*osExec.Cmd, error) {
return constellationCmd, nil
}
func runTessera() (*osExec.Cmd, error) {
tesseraVersion := "0.6"
// make sure JRE is available
if err := osExec.Command("java").Start(); err != nil {
return nil, fmt.Errorf("runTessera: java not available - %s", err.Error())
}
// download binary from github/release
dir, err := ioutil.TempDir("", "tessera")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir)
resp, err := http.Get(fmt.Sprintf("https://github.com/jpmorganchase/tessera/releases/download/tessera-%s/tessera-app-%s-app.jar", tesseraVersion, tesseraVersion))
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
tesseraJar := filepath.Join(dir, "tessera.jar")
if err := ioutil.WriteFile(tesseraJar, data, os.FileMode(0644)); err != nil {
return nil, err
}
// create config.json file
here, err := os.Getwd()
if err != nil {
return nil, err
}
if err = os.MkdirAll(path.Join(dir, "qdata"), 0755); err != nil {
return nil, err
}
tmIPCFile := filepath.Join(dir, "qdata", "tm.ipc")
keyData, err := ioutil.ReadFile(filepath.Join(here, "constellation-test-keys", "tm1.key"))
if err != nil {
return nil, err
}
publicKeyData, err := ioutil.ReadFile(filepath.Join(here, "constellation-test-keys", "tm1.pub"))
if err != nil {
return nil, err
}
tesseraConfigFile := filepath.Join(dir, "config.json")
if err := ioutil.WriteFile(tesseraConfigFile, []byte(fmt.Sprintf(`
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:./qdata/c0/db0;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0"
},
"server": {
"port": 9000,
"hostName": "http://localhost",
"sslConfig": {
"tls": "OFF",
"generateKeyStoreIfNotExisted": true,
"serverKeyStore": "./qdata/c1/server1-keystore",
"serverKeyStorePassword": "quorum",
"serverTrustStore": "./qdata/c1/server-truststore",
"serverTrustStorePassword": "quorum",
"serverTrustMode": "TOFU",
"knownClientsFile": "./qdata/c1/knownClients",
"clientKeyStore": "./c1/client1-keystore",
"clientKeyStorePassword": "quorum",
"clientTrustStore": "./c1/client-truststore",
"clientTrustStorePassword": "quorum",
"clientTrustMode": "TOFU",
"knownServersFile": "./qdata/c1/knownServers"
}
},
"peer": [
{
"url": "http://localhost:9000"
}
],
"keys": {
"passwords": [],
"keyData": [
{
"config": %s,
"publicKey": "%s"
}
]
},
"alwaysSendTo": [],
"unixSocketFile": "%s"
}
`, string(keyData), string(publicKeyData), tmIPCFile)), os.FileMode(0644)); err != nil {
return nil, err
}
cmdStatusChan := make(chan error)
cmd := osExec.Command("java", "-Xms128M", "-Xmx128M", "-jar", tesseraJar, "-configFile", tesseraConfigFile)
// run tessera
go func() {
err := cmd.Start()
cmdStatusChan <- err
}()
// wait for tessera to come up
go func() {
waitingErr := errors.New("waiting")
checkFunc := func() error {
conn, err := net.Dial("unix", tmIPCFile)
if err != nil {
return waitingErr
}
if _, err := conn.Write([]byte("GET /upcheck HTTP/1.0\r\n\r\n")); err != nil {
return waitingErr
}
result, err := ioutil.ReadAll(conn)
if err != nil || string(result) != "I'm up!" {
return waitingErr
}
return nil
}
for {
time.Sleep(3 * time.Second)
if err := checkFunc(); err != nil && err != waitingErr {
cmdStatusChan <- err
}
}
}()
if err := <-cmdStatusChan; err != nil {
return nil, err
}
// wait until tessera is up
return cmd, nil
}
// 600a600055600060006001a1
// [1] PUSH1 0x0a (store value)
// [3] PUSH1 0x00 (store addr)
@ -147,7 +282,13 @@ func TestPrivateTransaction(t *testing.T) {
constellationCmd, err := runConstellation()
if err != nil {
t.Fatal(err)
if strings.Contains(err.Error(), "executable file not found") {
if constellationCmd, err = runTessera(); err != nil {
t.Fatal(err)
}
} else {
t.Fatal(err)
}
}
defer constellationCmd.Process.Kill()

View File

@ -317,3 +317,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")
}
}

View File

@ -115,6 +115,7 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, privateState, config, cfg)
// Apply the transaction to the current state (included in the env)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
@ -129,9 +130,13 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common
}
*usedGas += gas
// If this is a private transaction, the public receipt should always
// indicate success.
publicFailed := !(config.IsQuorum && tx.IsPrivate()) && 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 wether the root touch-delete accounts.
receipt := types.NewReceipt(root, failed, *usedGas)
receipt := types.NewReceipt(root, publicFailed, *usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = gas
// if the transaction created a contract, store the creation address in the receipt.

Binary file not shown.

View File

@ -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

View File

@ -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).
- <strike>`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.</strike>
- `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<String>` - (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).
- <strike>`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.</strike>
- `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<String>` - (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
@ -48,3 +48,100 @@ web3.eth.sendTransaction({
}
});
```
***
## JSON RPC Privacy API Reference
__In addition to the JSON-RPC provided by Ethereum, Quorum exposes below two API calls.__
#### eth_storageRoot
Returns the storage root of given address (Contract/Account etc)
##### Parameters
address, block number (hex)
##### Returns
`String` - 32 Bytes storageroot hash as HEX string at latest block height. When blocknumber is given, it provides the storageroot hash at that block height.
##### Example
```js
// Request
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17"], "id": 67}'
// Response
{
"id":67,
"jsonrpc": "2.0",
"result": "0x81d1fa699f807735499cf6f7df860797cf66f6a66b565cfcda3fae3521eb6861"
}
```
```js
// When block number is provided...
// Request
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17","0x1"], "id": 67}'
// Response
{
"id":67,
"jsonrpc": "2.0",
"result": "0x81d1fa699f807735499cf6f7df860797cf66f6a66b565cfcda3fae3521eb6861"
}
// After private state of the contract is changed from '42' to '99'
// Request
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc": "2.0", "method": "eth_storageRoot", "params":["0x1349f3e1b8d71effb47b840594ff27da7e603d17","0x2"], "id": 67}'
// Response
{
"id":67,
"jsonrpc": "2.0",
"result": "0x0edb0e520c35df37a0d080d5245c9b8f9e1f9d7efab77c067d1e12c0a71299da"
}
```
***
#### eth_getQuorumPayload
Returns the unencrypted payload from Tessera/constellation
##### Parameters
Transaction payload hash in Hex format
##### Returns
`String` - unencrypted transaction payload in HEX format.
##### Example
```js
// Request
curl -X POST http://127.0.0.1:22000 --data '{"jsonrpc":"2.0", "method":"eth_getQuorumPayload", "params":["0x5e902fa2af51b186468df6ffc21fd2c26235f4959bf900fc48c17dc1774d86d046c0e466230225845ddf2cf98f23ede5221c935aac27476e77b16604024bade0"], "id":67}'
// Response
{
"id":67,
"jsonrpc": "2.0",
"result": "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a"
}
// On a node which is not party to the transaction
// Response
{
"id":67,
"jsonrpc": "2.0",
"result": "0x"
}
```

View File

@ -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.

View File

@ -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.

View File

@ -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 `<data-dir>/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 `<data-dir>/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.

View File

@ -126,6 +126,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,

View File

@ -44,7 +44,7 @@ var DefaultConfig = Config{
DatasetsInMem: 1,
DatasetsOnDisk: 2,
},
NetworkId: 1,
NetworkId: 1337,
LightPeers: 100,
DatabaseCache: 768,
TrieCache: 256,

View File

@ -1744,7 +1744,6 @@ func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) {
peer: tester.downloader.peers.peers["peer"].peer,
tester: tester,
}
if err := tester.sync("peer", nil, mode); err != nil {
t.Errorf("test %d: sync failed: %v", i, err)
}

View File

@ -665,7 +665,11 @@ web3._extend({
new web3._extend.Property({
name: 'leader',
getter: 'raft_leader'
})
}),
new web3._extend.Property({
name: 'cluster',
getter: 'raft_cluster'
}),
]
})
`

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -155,7 +155,7 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase com
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
}
if _, ok := engine.(consensus.Istanbul); ok || !config.IsQuorum {
if _, ok := engine.(consensus.Istanbul); ok || !config.IsQuorum || config.Clique != nil {
// Subscribe TxPreEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// Subscribe events for blockchain

View File

@ -261,13 +261,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
}
@ -277,7 +277,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)
}
@ -290,9 +290,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)
}

View File

@ -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)
}

View File

@ -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.

View File

@ -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)
}

View File

@ -35,5 +35,10 @@ func (s *PublicRaftAPI) Leader() (string, error) {
if nil != err {
return "", err
}
return addr.nodeId.String(), nil
return addr.NodeId.String(), nil
}
func (s *PublicRaftAPI) Cluster() []*Address {
nodeInfo := s.raftService.raftProtocolManager.NodeInfo()
return append(nodeInfo.PeerAddresses, nodeInfo.Address)
}

View File

@ -274,17 +274,28 @@ func (pm *ProtocolManager) isRaftIdUsed(raftId uint16) bool {
return pm.peers[raftId] != nil
}
func (pm *ProtocolManager) isP2pNodeInCluster(node *discover.Node) bool {
func (pm *ProtocolManager) isNodeAlreadyInCluster(node *discover.Node) error {
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, peer := range pm.peers {
if peer.p2pNode.ID == node.ID {
return true
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: %v", 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 == 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 false
return nil
}
func (pm *ProtocolManager) ProposeNewPeer(enodeId string) (uint16, error) {
@ -293,10 +304,6 @@ func (pm *ProtocolManager) ProposeNewPeer(enodeId string) (uint16, error) {
return 0, err
}
if pm.isP2pNodeInCluster(node) {
return 0, fmt.Errorf("node is already in the cluster: %v", enodeId)
}
if len(node.IP) != 4 {
return 0, fmt.Errorf("expected IPv4 address (with length 4), but got IP of length %v", len(node.IP))
}
@ -305,6 +312,10 @@ func (pm *ProtocolManager) ProposeNewPeer(enodeId string) (uint16, error) {
return 0, fmt.Errorf("enodeId is missing raftport querystring parameter: %v", enodeId)
}
if err := pm.isNodeAlreadyInCluster(node); err != nil {
return 0, err
}
raftId := pm.nextRaftId()
address := newAddress(raftId, node.RaftPort, node)
@ -624,17 +635,17 @@ func (pm *ProtocolManager) entriesToApply(allEntries []raftpb.Entry) (entriesToA
}
func raftUrl(address *Address) string {
return fmt.Sprintf("http://%s:%d", address.ip, address.raftPort)
return fmt.Sprintf("http://%s:%d", address.Ip, address.RaftPort)
}
func (pm *ProtocolManager) addPeer(address *Address) {
pm.mu.Lock()
defer pm.mu.Unlock()
raftId := address.raftId
raftId := address.RaftId
// Add P2P connection:
p2pNode := discover.NewNode(address.nodeId, address.ip, 0, uint16(address.p2pPort))
p2pNode := discover.NewNode(address.NodeId, address.Ip, 0, uint16(address.P2pPort))
pm.p2pServer.AddPeer(p2pNode)
// Add raft transport connection:

View File

@ -5,28 +5,29 @@ import (
"net"
"fmt"
"log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rlp"
"log"
)
// Serializable information about a Peer. Sufficient to build `etcdRaft.Peer`
// or `discover.Node`.
type Address struct {
raftId uint16
nodeId discover.NodeID
ip net.IP
p2pPort uint16
raftPort uint16
RaftId uint16 `json:"raftId"`
NodeId discover.NodeID `json:"nodeId"`
Ip net.IP `json:"ip"`
P2pPort uint16 `json:"p2pPort"`
RaftPort uint16 `json:"raftPort"`
}
func newAddress(raftId uint16, raftPort uint16, node *discover.Node) *Address {
return &Address{
raftId: raftId,
nodeId: node.ID,
ip: node.IP,
p2pPort: node.TCP,
raftPort: raftPort,
RaftId: raftId,
NodeId: node.ID,
Ip: node.IP,
P2pPort: node.TCP,
RaftPort: raftPort,
}
}
@ -37,7 +38,7 @@ type Peer struct {
}
func (addr *Address) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{addr.raftId, addr.nodeId, addr.ip, addr.p2pPort, addr.raftPort})
return rlp.Encode(w, []interface{}{addr.RaftId, addr.NodeId, addr.Ip, addr.P2pPort, addr.RaftPort})
}
func (addr *Address) DecodeRLP(s *rlp.Stream) error {
@ -53,7 +54,7 @@ func (addr *Address) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode(&temp); err != nil {
return err
} else {
addr.raftId, addr.nodeId, addr.ip, addr.p2pPort, addr.raftPort = temp.RaftId, temp.NodeId, temp.Ip, temp.P2pPort, temp.RaftPort
addr.RaftId, addr.NodeId, addr.Ip, addr.P2pPort, addr.RaftPort = temp.RaftId, temp.NodeId, temp.Ip, temp.P2pPort, temp.RaftPort
return nil
}
}

View File

@ -30,7 +30,7 @@ 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 (a ByRaftId) Less(i, j int) bool { return a[i].RaftId < a[j].RaftId }
func (pm *ProtocolManager) buildSnapshot() *Snapshot {
pm.mu.RLock()
@ -140,17 +140,17 @@ func (pm *ProtocolManager) updateClusterMembership(newConfState raftpb.ConfState
for _, tempAddress := range addresses {
address := tempAddress // Allocate separately on the heap for each iteration.
if address.raftId == pm.raftId {
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]
existingPeer := pm.peers[address.RaftId]
pm.mu.RUnlock()
if existingPeer == nil {
log.Info("adding new raft peer", "raft id", address.raftId)
log.Info("adding new raft peer", "raft id", address.RaftId)
pm.addPeer(&address)
}
}