Merge remote-tracking branch 'remotes/origin/master' into permissions-master-merge

# Conflicts:
#	params/config.go
This commit is contained in:
vsmk98 2019-10-04 11:20:58 +08:00
commit 1db7d6a36b
32 changed files with 536 additions and 177 deletions

View File

@ -81,7 +81,9 @@ type ContractTransactor interface {
// for setting a reasonable default.
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
// SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error
SendTransaction(ctx context.Context, tx *types.Transaction, args PrivateTxArgs) error
// PreparePrivateTransaction send the private transaction to Tessera/Constellation's /storeraw API using HTTP
PreparePrivateTransaction(data []byte, privateFrom string) ([]byte, error)
}
// ContractFilterer defines the methods needed to access log events using one-off

View File

@ -309,7 +309,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// SendTransaction updates the pending block to include the given transaction.
// It panics if the transaction is invalid.
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction, args bind.PrivateTxArgs) error {
b.mu.Lock()
defer b.mu.Unlock()
@ -335,6 +335,11 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
return nil
}
// PreparePrivateTransaction dummy implementation
func (b *SimulatedBackend) PreparePrivateTransaction(data []byte, privateFrom string) ([]byte, error) {
return data, nil
}
// FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//

View File

@ -34,6 +34,13 @@ import (
// sign the transaction before submission.
type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error)
// Quorum
//
// Additional arguments in order to support transaction privacy
type PrivateTxArgs struct {
PrivateFor []string `json:"privateFor"`
}
// CallOpts is the collection of options to fine tune a contract call request.
type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one
@ -54,6 +61,10 @@ type TransactOpts struct {
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
// Quorum
PrivateFrom string // The public key of the Tessera/Constellation identity to send this tx from.
PrivateFor []string // The public keys of the Tessera/Constellation identities this tx is intended for.
}
// FilterOpts is the collection of options to fine tune filtering for events
@ -231,16 +242,36 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
} else {
rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input)
}
// If this transaction is private, we need to substitute the data payload
// with the hash of the transaction from tessera/constellation.
if opts.PrivateFor != nil {
var payload []byte
payload, err = c.transactor.PreparePrivateTransaction(rawTx.Data(), opts.PrivateFrom)
if err != nil {
return nil, err
}
rawTx = c.createPrivateTransaction(rawTx, payload)
}
// Choose signer to sign transaction
if opts.Signer == nil {
return nil, errors.New("no signer to authorize the transaction with")
}
signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx)
var signedTx *types.Transaction
if rawTx.IsPrivate() {
signedTx, err = opts.Signer(types.QuorumPrivateTxSigner{}, opts.From, rawTx)
} else {
signedTx, err = opts.Signer(types.HomesteadSigner{}, opts.From, rawTx)
}
if err != nil {
return nil, err
}
if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx, PrivateTxArgs{PrivateFor: opts.PrivateFor}); err != nil {
return nil, err
}
return signedTx, nil
}
@ -340,6 +371,18 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
return parseTopics(out, indexed, log.Topics[1:])
}
// createPrivateTransaction replaces the payload of private transaction to the hash from Tessera/Constellation
func (c *BoundContract) createPrivateTransaction(tx *types.Transaction, payload []byte) *types.Transaction {
var privateTx *types.Transaction
if tx.To() == nil {
privateTx = types.NewContractCreation(tx.Nonce(), tx.Value(), tx.Gas(), tx.GasPrice(), payload)
} else {
privateTx = types.NewTransaction(tx.Nonce(), c.address, tx.Value(), tx.Gas(), tx.GasPrice(), payload)
}
privateTx.SetPrivate()
return privateTx
}
// ensureContext is a helper method to ensure a context is not nil, even if the
// user specified it as such.
func ensureContext(ctx context.Context) context.Context {

View File

@ -76,7 +76,7 @@ func TestWaitDeployed(t *testing.T) {
}()
// Send and mine the transaction.
backend.SendTransaction(ctx, tx)
backend.SendTransaction(ctx, tx, bind.PrivateTxArgs{})
backend.Commit()
select {

View File

@ -41,6 +41,8 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
@ -483,7 +485,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
continue
}
// Submit the transaction and mark as funded if successful
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
if err := f.client.SendTransaction(context.Background(), signed, bind.PrivateTxArgs{}); err != nil {
f.lock.Unlock()
if err = sendError(conn, err); err != nil {
log.Warn("Failed to send transaction transmission error to client", "err", err)

View File

@ -27,8 +27,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/istanbul"
istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core"
"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/sha3"
@ -289,8 +289,8 @@ func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *typ
}
}
// The length of validSeal should be larger than number of faulty node + 1
if validSeal <= 2*snap.ValSet.F() {
// The length of validSeal should be larger than number of faulty node + 1
if validSeal <= snap.ValSet.F() {
return errInvalidCommittedSeals
}
@ -416,42 +416,45 @@ func (sb *backend) Seal(chain consensus.ChainReader, block *types.Block, results
return err
}
// wait for the timestamp of header, use this to adjust the block period
delay := time.Unix(block.Header().Time.Int64(), 0).Sub(now())
select {
case <-time.After(delay):
case <-stop:
results <- nil
return nil
}
delay := time.Unix(header.Time.Int64(), 0).Sub(now())
// get the proposed block hash and clear it if the seal() is completed.
sb.sealMu.Lock()
sb.proposedBlockHash = block.Hash()
clear := func() {
sb.proposedBlockHash = common.Hash{}
sb.sealMu.Unlock()
}
defer clear()
// post block into Istanbul engine
go sb.EventMux().Post(istanbul.RequestEvent{
Proposal: block,
})
for {
go func() {
// wait for the timestamp of header, use this to adjust the block period
select {
case result := <-sb.commitCh:
// if the block hash and the hash from channel are the same,
// return the result. Otherwise, keep waiting the next hash.
if result != nil && block.Hash() == result.Hash() {
results <- result
return nil
}
case <-time.After(delay):
case <-stop:
results <- nil
return nil
return
}
}
// get the proposed block hash and clear it if the seal() is completed.
sb.sealMu.Lock()
sb.proposedBlockHash = block.Hash()
defer func() {
sb.proposedBlockHash = common.Hash{}
sb.sealMu.Unlock()
}()
// post block into Istanbul engine
go sb.EventMux().Post(istanbul.RequestEvent{
Proposal: block,
})
for {
select {
case result := <-sb.commitCh:
// if the block hash and the hash from channel are the same,
// return the result. Otherwise, keep waiting the next hash.
if result != nil && block.Hash() == result.Hash() {
results <- result
return
}
case <-stop:
results <- nil
return
}
}
}()
return nil
}
// update timestamp and signature of the block based on its number of transactions

View File

@ -195,30 +195,44 @@ func TestSealCommittedOtherHash(t *testing.T) {
chain, engine := newBlockChain(4)
block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
otherBlock := makeBlockWithoutSeal(chain, engine, block)
expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...)
eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{})
eventLoop := func() {
blockOutputChannel := make(chan *types.Block)
stopChannel := make(chan struct{})
go func() {
select {
case ev := <-eventSub.Chan():
_, ok := ev.Data.(istanbul.RequestEvent)
if !ok {
if _, ok := ev.Data.(istanbul.RequestEvent); !ok {
t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data))
}
engine.Commit(otherBlock, [][]byte{})
if err := engine.Commit(otherBlock, [][]byte{expectedCommittedSeal}); err != nil {
t.Error(err.Error())
}
}
eventSub.Unsubscribe()
}
go eventLoop()
seal := func() {
engine.Seal(chain, block, nil, make(chan struct{}))
t.Error("seal should not be completed")
}
go seal()
}()
go func() {
if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil {
t.Error(err.Error())
}
}()
const timeoutDura = 2 * time.Second
timeout := time.NewTimer(timeoutDura)
select {
case <-timeout.C:
// wait 2 seconds to ensure we cannot get any blocks from Istanbul
case <-blockOutputChannel:
t.Error("Wrong block found!")
default:
//no block found, stop the sealing
close(stopChannel)
}
select {
case output := <-blockOutputChannel:
if output != nil {
t.Error("Block not nil!")
}
}
}

View File

@ -16,6 +16,8 @@
package istanbul
import "math/big"
type ProposerPolicy uint64
const (
@ -28,6 +30,7 @@ type Config struct {
BlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between two consecutive block's timestamps in second
ProposerPolicy ProposerPolicy `toml:",omitempty"` // The policy for proposer selection
Epoch uint64 `toml:",omitempty"` // The number of blocks after which to checkpoint and reset the pending votes
Ceil2Nby3Block *big.Int `toml:",omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)]
}
var DefaultConfig = &Config{
@ -35,4 +38,5 @@ var DefaultConfig = &Config{
BlockPeriod: 1,
ProposerPolicy: RoundRobin,
Epoch: 30000,
Ceil2Nby3Block: big.NewInt(0),
}

View File

@ -72,7 +72,7 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error {
//
// If we already have a proposal, we may have chance to speed up the consensus process
// by committing the proposal without PREPARE messages.
if c.current.Commits.Size() > 2*c.valSet.F() && c.state.Cmp(StateCommitted) < 0 {
if c.current.Commits.Size() >= c.QuorumSize() && c.state.Cmp(StateCommitted) < 0 {
// Still need to call LockHash here since state can skip Prepared state and jump directly to the Committed state.
c.current.LockHash()
c.commit()

View File

@ -191,8 +191,8 @@ OUTER:
if r0.state != StatePrepared {
t.Errorf("state mismatch: have %v, want %v", r0.state, StatePrepared)
}
if r0.current.Commits.Size() > 2*r0.valSet.F() {
t.Errorf("the size of commit messages should be less than %v", 2*r0.valSet.F()+1)
if r0.current.Commits.Size() >= r0.QuorumSize() {
t.Errorf("the size of commit messages should be less than %v", r0.QuorumSize())
}
if r0.current.IsHashLocked() {
t.Errorf("block should not be locked")
@ -200,12 +200,12 @@ OUTER:
continue
}
// core should have 2F+1 prepare messages
if r0.current.Commits.Size() <= 2*r0.valSet.F() {
t.Errorf("the size of commit messages should be larger than 2F+1: size %v", r0.current.Commits.Size())
// core should have 2F+1 before Ceil2Nby3Block or Ceil(2N/3) prepare messages
if r0.current.Commits.Size() < r0.QuorumSize() {
t.Errorf("the size of commit messages should be larger than 2F+1 or Ceil(2N/3): size %v", r0.QuorumSize())
}
// check signatures large than 2F+1
// check signatures large than F
signedCount := 0
committedSeals := v0.committedMsgs[0].committedSeals
for _, validator := range r0.valSet.List() {
@ -216,8 +216,8 @@ OUTER:
}
}
}
if signedCount <= 2*r0.valSet.F() {
t.Errorf("the expected signed count should be larger than %v, but got %v", 2*r0.valSet.F(), signedCount)
if signedCount <= r0.valSet.F() {
t.Errorf("the expected signed count should be larger than %v, but got %v", r0.valSet.F(), signedCount)
}
if !r0.current.IsHashLocked() {
t.Errorf("block should be locked")

View File

@ -342,6 +342,15 @@ func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address,
return istanbul.CheckValidatorSignature(c.valSet, data, sig)
}
func (c *core) QuorumSize() int {
if c.config.Ceil2Nby3Block == nil || (c.current != nil && c.current.sequence.Cmp(c.config.Ceil2Nby3Block) < 0) {
c.logger.Trace("Confirmation Formula used 2F+ 1")
return (2 * c.valSet.F()) + 1
}
c.logger.Trace("Confirmation Formula used ceil(2N/3)")
return int(math.Ceil(float64(2*c.valSet.Size()) / 3))
}
// PrepareCommittedSeal returns a committed seal for the given hash
func PrepareCommittedSeal(hash common.Hash) []byte {
var buf bytes.Buffer

View File

@ -17,6 +17,7 @@
package core
import (
"github.com/ethereum/go-ethereum/common"
"math/big"
"reflect"
"testing"
@ -80,3 +81,20 @@ func TestNewRequest(t *testing.T) {
}
}
}
func TestQuorumSize(t *testing.T) {
N := uint64(4)
F := uint64(1)
sys := NewTestSystemWithBackend(N, F)
backend := sys.backends[0]
c := backend.engine.(*core)
valSet := c.valSet
for i := 1; i <= 1000; i++ {
valSet.AddValidator(common.StringToAddress(string(i)))
if 2*c.QuorumSize() <= (valSet.Size()+valSet.F()) || 2*c.QuorumSize() > (valSet.Size()+valSet.F()+2) {
t.Errorf("quorumSize constraint failed, expected value (2*QuorumSize > Size+F && 2*QuorumSize <= Size+F+2) to be:%v, got: %v, for size: %v", true, false, valSet.Size())
}
}
}

View File

@ -59,7 +59,7 @@ func (c *core) handlePrepare(msg *message, src istanbul.Validator) error {
// Change to Prepared state if we've received enough PREPARE messages or it is locked
// and we are in earlier state before Prepared state.
if ((c.current.IsHashLocked() && prepare.Digest == c.current.GetLockedHash()) || c.current.GetPrepareOrCommitSize() > 2*c.valSet.F()) &&
if ((c.current.IsHashLocked() && prepare.Digest == c.current.GetLockedHash()) || c.current.GetPrepareOrCommitSize() >= c.QuorumSize()) &&
c.state.Cmp(StatePrepared) < 0 {
c.current.LockHash()
c.setState(StatePrepared)

View File

@ -17,6 +17,7 @@
package core
import (
"math"
"math/big"
"reflect"
"testing"
@ -156,12 +157,11 @@ func TestHandlePrepare(t *testing.T) {
errInconsistentSubject,
},
{
// less than 2F+1
func() *testSystem {
sys := NewTestSystemWithBackend(N, F)
// save less than 2*F+1 replica
sys.backends = sys.backends[2*int(F)+1:]
// save less than Ceil(2*N/3) replica
sys.backends = sys.backends[int(math.Ceil(float64(2*N)/3)):]
for i, backend := range sys.backends {
c := backend.engine.(*core)
@ -214,8 +214,8 @@ OUTER:
if r0.state != StatePreprepared {
t.Errorf("state mismatch: have %v, want %v", r0.state, StatePreprepared)
}
if r0.current.Prepares.Size() > 2*r0.valSet.F() {
t.Errorf("the size of PREPARE messages should be less than %v", 2*r0.valSet.F()+1)
if r0.current.Prepares.Size() >= r0.QuorumSize() {
t.Errorf("the size of PREPARE messages should be less than %v", r0.QuorumSize())
}
if r0.current.IsHashLocked() {
t.Errorf("block should not be locked")
@ -224,12 +224,12 @@ OUTER:
continue
}
// core should have 2F+1 PREPARE messages
if r0.current.Prepares.Size() <= 2*r0.valSet.F() {
t.Errorf("the size of PREPARE messages should be larger than 2F+1: size %v", r0.current.Commits.Size())
// core should have 2F+1 before Ceil2Nby3Block and Ceil(2N/3) after Ceil2Nby3Block PREPARE messages
if r0.current.Prepares.Size() < r0.QuorumSize() {
t.Errorf("the size of PREPARE messages should be larger than 2F+1 or ceil(2N/3): size %v", r0.current.Commits.Size())
}
// a message will be delivered to backend if 2F+1
// a message will be delivered to backend if ceil(2N/3)
if int64(len(v0.sentMsgs)) != 1 {
t.Errorf("the Send() should be called once: times %v", len(test.system.backends[0].sentMsgs))
}

View File

@ -82,9 +82,9 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error {
// Verify the proposal we received
if duration, err := c.backend.Verify(preprepare.Proposal); err != nil {
logger.Warn("Failed to verify proposal", "err", err, "duration", duration)
// if it's a future block, we will handle it again after the duration
if err == consensus.ErrFutureBlock {
logger.Info("Proposed block will be handled in the future", "err", err, "duration", duration)
c.stopFuturePreprepareTimer()
c.futurePreprepareTimer = time.AfterFunc(duration, func() {
c.sendEvent(backlogEvent{
@ -93,6 +93,7 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error {
})
})
} else {
logger.Warn("Failed to verify proposal", "err", err, "duration", duration)
c.sendNextRoundChange()
}
return err

View File

@ -98,8 +98,8 @@ func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error {
c.sendRoundChange(roundView.Round)
}
return nil
} else if num == int(2*c.valSet.F()+1) && (c.waitingForRoundChange || cv.Round.Cmp(roundView.Round) < 0) {
// We've received 2f+1 ROUND CHANGE messages, start a new round immediately.
} else if num == c.QuorumSize() && (c.waitingForRoundChange || cv.Round.Cmp(roundView.Round) < 0) {
// We've received 2f+1/Ceil(2N/3) ROUND CHANGE messages, start a new round immediately.
c.startNewRound(roundView.Round)
return nil
} else if cv.Round.Cmp(roundView.Round) < 0 {

View File

@ -2,13 +2,14 @@
## Introduction
The link attached holds an implementation of a [Raft](https://raft.github.io)-based consensus mechanism (using [etcd](https://github.com/coreos/etcd)'s [Raft implementation](https://github.com/coreos/etcd/tree/master/raft)) as an alternative to Ethereum's default proof-of-work. This is useful for closed-membership/consortium settings where byzantine fault tolerance is not a requirement, and there is a desire for faster blocktimes (on the order of milliseconds instead of seconds) and transaction finality (the absence of forking.) Also, compared with QuorumChain, this consensus mechanism does not "unnecessarily" create empty blocks, and effectively creates blocks "on-demand."
Quorum includes an implementation of a [Raft](https://raft.github.io)-based consensus mechanism (using [etcd](https://github.com/coreos/etcd)'s [Raft implementation](https://github.com/coreos/etcd/tree/master/raft)) as an alternative to Ethereum's default proof-of-work. This is useful for closed-membership/consortium settings where byzantine fault tolerance is not a requirement, and there is a desire for faster blocktimes (on the order of milliseconds instead of seconds) and transaction finality (the absence of forking.) This consensus mechanism does not "unnecessarily" create empty blocks, and effectively creates blocks "on-demand."
When the `geth` binary is passed the `--raft` flag, the node will operate in "raft mode."
## Some implementation basics
Note: Though we use the etcd implementation of the Raft protocol, we speak of "Raft" more broadly to refer to the Raft protocol, and its use to achieve consensus for Quorum/Ethereum.
!!! note
Though we use the etcd implementation of the Raft protocol, we speak of "Raft" more broadly to refer to the Raft protocol, and its use to achieve consensus for Quorum/Ethereum.
Both Raft and Ethereum have their own notion of a "node":
@ -25,7 +26,7 @@ verifier | follower
The main reasons we co-locate the leader and minter are (1) convenience, in that Raft ensures there is only one leader at a time, and (2) to avoid a network hop from a node minting blocks to the leader, through which all Raft writes must flow. Our implementation watches Raft leadership changes -- if a node becomes a leader it will start minting, and if a node loses its leadership, it will stop minting.
An observant reader might note that during raft leadership transitions, there could be a small period of time where more than one node might assume that it has minting duties; we detail how correctness is preserved in more detail later in this document.
An observant reader might note that during raft leadership transitions, there could be a small period of time where more than one node might assume that it has minting duties; we detail how correctness is preserved in the [Chain extension, races, and correctness](#chain-extension-races-and-correctness) section.
We use the existing Ethereum p2p transport layer to communicate transactions between nodes, but we communicate blocks only through the Raft transport layer. They are created by the minter and flow from there to the rest of the cluster, always in the same order, via Raft.
@ -45,7 +46,7 @@ Let's follow the lifecycle of a typical transaction:
#### on the minter:
3. It reaches the minter, where it's included in the next block (see `mintNewBlock`) via the transaction pool.
4. Block creation triggers a [`NewMinedBlockEvent`](https://godoc.org/github.com/jpmorganchase/quorum/core#NewMinedBlockEvent), which the Raft protocol manager receives via its subscription `minedBlockSub`. The `minedBroadcastLoop` (in raft/handler.go) puts this new block to the `ProtocolManager.blockProposalC` channel.
4. Block creation triggers a [`NewMinedBlockEvent`](https://godoc.org/github.com/jpmorganchase/quorum/core#NewMinedBlockEvent), which the Raft protocol manager receives via its subscription `minedBlockSub`. The `minedBroadcastLoop` (in `raft/handler.go`) puts this new block to the `ProtocolManager.blockProposalC` channel.
5. `serveLocalProposals` is waiting at the other end of the channel. Its job is to RLP-encode blocks and propose them to Raft. Once it flows through Raft, this block will likely become the new head of the blockchain (on all nodes.)
#### on every node:
@ -57,9 +58,9 @@ Let's follow the lifecycle of a typical transaction:
8. The block is now handled by `applyNewChainHead`. This method checks whether the block extends the chain (i.e. it's parent is the current head of the chain; see below). If it does not extend the chain, it is simply ignored as a no-op. If it does extend chain, the block is validated and then written as the new head of the chain by [`InsertChain`](https://godoc.org/github.com/jpmorganchase/quorum/core#BlockChain.InsertChain).
9. A [`ChainHeadEvent`](https://godoc.org/github.com/jpmorganchase/quorum/core#ChainHeadEvent) is posted to notify listeners that a new block has been accepted. This is relevant to us because:
* It removes the relevant transaction from the transaction pool.
* It removes the relevant transaction from `speculativeChain`'s `proposedTxes` (see below).
* It triggers `requestMinting` in (minter.go), telling the node to schedule the minting of a new block if any more transactions are pending.
* It removes the relevant transaction from the transaction pool.
* It removes the relevant transaction from `speculativeChain`'s `proposedTxes` (see below).
* It triggers `requestMinting` in (`minter.go`), telling the node to schedule the minting of a new block if any more transactions are pending.
The transaction is now available on all nodes in the cluster with complete finality. Because Raft guarantees a single ordering of entries stored in its log, and because everything that is committed is guaranteed to remain so, there is no forking of the blockchain built upon Raft.
@ -118,13 +119,13 @@ This default of 50ms is configurable via the `--raftblocktime` flag to geth.
One of the ways our approach differs from vanilla Ethereum is that we introduce a new concept of "speculative minting." This is not strictly required for the core functionality of Raft-based Ethereum consensus, but rather it is an optimization that affords lower latency between blocks (or: faster transaction "finality.")
It takes some time for a block to flow through Raft (consensus) to become the head of the chain. If we synchronously waited for a block to become the new head of the chain before creating the new block, any transactions that we receive would take more time to make it into the chain.
It takes some time for a block to flow through Raft (consensus) and become the head of the chain. If we synchronously waited for a block to become the new head of the chain before creating the new block, any transactions that we receive would take more time to make it into the chain.
In speculative minting we allow the creation of a new block (and its proposal to Raft) before its parent has made it all the way through Raft and into the blockchain.
Since this can happen repeatedly, these blocks (which each have a reference to their parent block) can form a sort of chain. We call this a "speculative chain."
During the course of operation that a speculative chain forms, we keep track of the subset of transactions in the pool that we have already put into blocks (in the speculative chain) that have not yet made it into the blockchain (and whereupon a [`core.ChainHeadEvent`](https://godoc.org/github.com/jpmorganchase/quorum/core#ChainHeadEvent) occurs.) These are called "proposed transactions" (see speculative_chain.go).
During the course of operation that a speculative chain forms, we keep track of the subset of transactions in the pool that we have already put into blocks (in the speculative chain) that have not yet made it into the blockchain (and whereupon a [`core.ChainHeadEvent`](https://godoc.org/github.com/jpmorganchase/quorum/core#ChainHeadEvent) occurs.) These are called "proposed transactions" (see `speculative_chain.go`).
Per the presence of "races" (as we detail above), it is possible that a block somewhere in the middle of a speculative chain ends up not making into the chain. In this scenario an [`InvalidRaftOrdering`](https://godoc.org/github.com/jpmorganchase/quorum/raft#InvalidRaftOrdering) event will occur, and we clean up the state of the speculative chain accordingly.
@ -135,14 +136,14 @@ There is currently no limit to the length of these speculative chains, but we pl
* `head`: The last-created speculative block. This can be `nil` if the last-created block is already included in the blockchain.
* `proposedTxes`: The set of transactions which have been proposed to Raft in some block, but not yet included in the blockchain.
* `unappliedBlocks`: A queue of blocks which have been proposed to Raft but not yet committed to the blockchain.
- When minting a new block, we enqueue it at the end of this queue
- `accept` is called to remove the oldest speculative block when it's accepted into the blockchain.
- When an [`InvalidRaftOrdering`](https://godoc.org/github.com/jpmorganchase/quorum/raft#InvalidRaftOrdering) occurs, we unwind the queue by popping the most recent blocks from the "new end" of the queue until we find the invalid block. We must repeatedly remove these "newer" speculative blocks because they are all dependent on a block that we know has not been included in the blockchain.
- When minting a new block, we enqueue it at the end of this queue
- `accept` is called to remove the oldest speculative block when it's accepted into the blockchain.
- When an [`InvalidRaftOrdering`](https://godoc.org/github.com/jpmorganchase/quorum/raft#InvalidRaftOrdering) occurs, we unwind the queue by popping the most recent blocks from the "new end" of the queue until we find the invalid block. We must repeatedly remove these "newer" speculative blocks because they are all dependent on a block that we know has not been included in the blockchain.
* `expectedInvalidBlockHashes`: The set of blocks which build on an invalid block, but haven't passsed through Raft yet. We remove these as we get them back. When these non-extending blocks come back through Raft we remove them from the speculative chain. We use this set as a "guard" against trying to trim the speculative chain when we shouldn't.
## The Raft transport layer
We communicate blocks over the HTTP transport layer built in to etcd Raft. It's also (at least theoretically) possible to use p2p protocol built-in to Ethereum as a transport for Raft. In our testing we found the default etcd HTTP transport to be more reliable than the p2p (at least as implemented in geth) under high load.
We communicate blocks over the HTTP transport layer built in to etcd Raft. It's also (at least theoretically) possible to use the p2p protocol built-in to Ethereum as a transport for Raft. In our testing we found the default etcd HTTP transport to be more reliable than the p2p (at least as implemented in geth) under high load.
Quorum listens on port 50400 by default for the raft transport, but this is configurable with the `--raftport` flag.

View File

@ -60,7 +60,7 @@
* It saves one network call communicating the block to the leader.
* It provides a simple way to choose a minter. If we didn't use the Raft leader we'd have to build in "minter election" at a higher level.
Additionally there could even be multiple minters running at the same time, but this would produce contention for which blocks actually extend the chain, reducing the productivity of the cluster (see "races" above).
Additionally there could even be multiple minters running at the same time, but this would produce contention for which blocks actually extend the chain, reducing the productivity of the cluster (see [Raft: Chain extension, races, and correctness](../Consensus/raft/#chain-extension-races-and-correctness) above).
??? question "I thought there were no forks in a Raft-based blockchain. What's the deal with "speculative minting"?"
"Speculative chains" are not forks in the blockchain. They represent a series ("chain") of blocks that have been sent through Raft, after which each of the blocks may or may not actually end up being included in *the blockchain*.

View File

@ -1,7 +1,7 @@
Tessera uses cryptographic keys to provide transaction privacy.
You can use existing private/public key pairs as well as use Tessera to generate new key pairs for you. See [Generating & securing keys](../../Tessera%20Services/Keys/Keys) for more info.
```
```json
"keys": {
"passwords": [],
"passwordFile": "Path",
@ -16,7 +16,7 @@ You can use existing private/public key pairs as well as use Tessera to generate
},
"keyData": [
{
//The data for a private/public key pair
// The data for a private/public key pair
}
]
}
@ -25,11 +25,14 @@ You can use existing private/public key pairs as well as use Tessera to generate
## KeyData
Key pairs can be provided in several ways:
#### 1. Direct key pairs
Direct key pairs are convenient but are the least secure configuration option available, as you expose your private key in the configuration file. More secure options are available and preferable for production environments.
### Direct key pairs
The key pair data is provided in plain text in the configfile:
```
!!! warning
Direct key pairs and unprotected inline key pairs are convenient but are the least secure configuration options available as the private key is exposed in the configuration file. The other options available are more secure and recommended for production environments.
The key pair data is provided in plain text in the configfile.
```json
"keys": {
"keyData": [
{
@ -40,9 +43,14 @@ The key pair data is provided in plain text in the configfile:
}
```
#### 2. Inline key pairs
The public key is provided in plain text. The private key is provided through additional config:
```
### Inline key pairs
#### Unprotected
!!! warning
Direct key pairs and unprotected inline key pairs are convenient but are the least secure configuration options available as the private key is exposed in the configuration file. The other options available are more secure and recommended for production environments.
The key pair data is provided in plain text in the configfile. The plain text private key is provided in a `config` json object:
```json
"keys": {
"keyData": [
{
@ -58,11 +66,13 @@ The public key is provided in plain text. The private key is provided through a
}
```
This allows for the use of Argon2 password-secured private keys by including the corresponding Argon2 settings in the additional config:
#### Protected
The public key is provided in plain text. The private key must be password-protected using Argon2. The corresponding encrypted data is provided in the `config` json object.
```
```json
"keys": {
"passwords": ["password"],
"passwordFile": "/path/to/pwds.txt",
"keyData": [
{
"config": {
@ -85,9 +95,73 @@ This allows for the use of Argon2 password-secured private keys by including the
}
```
#### 3. Azure Key Vault key pairs
The keys in the pair are stored as secrets in an Azure Key Vault. This requires providing the vault url and the secret IDs for both keys:
Passwords must be provided so that Tessera can decrypt and use the private keys. Passwords can be provided in multiple ways:
| | Description |
|--------|--------------|
| File | `"passwordFile": "/path/to/pwds.txt"`<br/>Must contain only one password per line. Empty lines should be used for unlocked keys. Passwords must be provided in the order that key pairs are defined in the config. |
| Direct | `"passwords": ["pwd1", "pwd2", ...]`<br/>Empty strings should be used for unlocked keys. Passwords must be provided in the order that key pairs are defined in the config. Not recommended for production use. |
| CLI | Tessera will prompt on the CLI for the passwords of any encrypted keys that have not had passwords provided in the config. This process only needs to be performed once, when starting the node. |
### Filesystem key pairs
The keys in the pair are stored in files:
```json
"keys": {
"passwords": ["password"],
"passwordFile": "/path/to/pwds.txt",
"keyData": [
{
"privateKeyPath": "/path/to/privateKey.key",
"publicKeyPath": "/path/to/publicKey.pub"
}
]
}
```
The contents of the public key file must contain the public key only, e.g.:
```
/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=
```
The contents of the private key file must contain the private key in the Inline key pair format, e.g.:
```json
{
"type" : "unlocked",
"data" : {
"bytes" : "DK0HDgMWJKtZVaP31mPhk6TJNACfVzz7VZv2PsQZeKM="
}
}
```
or
```json
{
"data": {
"aopts": {
"variant": "id",
"memory": 1048576,
"iterations": 10,
"parallelism": 4,
},
"snonce": "x3HUNXH6LQldKtEv3q0h0hR4S12Ur9pC",
"asalt": "7Sem2tc6fjEfW3yYUDN/kSslKEW0e1zqKnBCWbZu2Zw=",
"sbox": "d0CmRus0rP0bdc7P7d/wnOyEW14pwFJmcLbdu2W3HmDNRWVJtoNpHrauA/Sr5Vxc"
},
"type": "argon2sbox"
}
```
Passwords must be provided so that Tessera can decrypt and use the private keys. Passwords can be provided in multiple ways:
| | Description |
|--------|--------------|
| File | `"passwordFile": "/path/to/pwds.txt"`<br/>Must contain only one password per line. Empty lines should be used for unlocked keys. Passwords must be provided in the order that key pairs are defined in the config. |
| Direct | `"passwords": ["pwd1", "pwd2", ...]`<br/>Empty strings should be used for unlocked keys. Passwords must be provided in the order that key pairs are defined in the config. Not recommended for production use. |
| CLI | Tessera will prompt on the CLI for the passwords of any encrypted keys that have not had passwords provided in the config. This process only needs to be performed once, when starting the node. |
### Azure Key Vault key pairs
The keys in the pair are stored as secrets in an Azure Key Vault. This requires providing the vault url and the secret IDs for both keys:
```json
"keys": {
"azureKeyVaultConfig": {
"url": "https://my-vault.vault.azure.net"
@ -105,11 +179,12 @@ The keys in the pair are stored as secrets in an Azure Key Vault. This requires
This example configuration will retrieve the specified versions of the secrets `Key` and `Pub` from the key vault with DNS name `https://my-vault.vault.azure.net`. If no version is specified then the latest version of the secret is retrieved.
> Environment variables must be set if using an Azure Key Vault, for more information see [Setting up an Azure Key Vault](../../Tessera%20Services/Keys/Setting%20up%20an%20Azure%20Key%20Vault)
!!! info
Environment variables must be set if using an Azure Key Vault, for more information see [Setting up an Azure Key Vault](../../Tessera%20Services/Keys/Setting%20up%20an%20Azure%20Key%20Vault)
#### 4. Hashicorp Vault key pairs
### Hashicorp Vault key pairs
The keys in the pair are stored as a secret in a Hashicorp Vault. Additional configuration can also be provided if the Vault is configured to use TLS and if the AppRole auth method is being used at a different path to the default (`approle`):
```
```json
"hashicorpKeyVaultConfig": {
"url": "https://localhost:8200",
"tlsKeyStorePath": "/path/to/keystore.jks",
@ -132,38 +207,13 @@ This example configuration will retrieve version 1 of the secret `engine/secret`
If no `hashicorpVaultSecretVersion` is provided then the latest version for the secret will be retrieved by default.
Tessera requires TLS certificates and keys to be stored in `.jks` Java keystore format. If the `.jks` files are password protected then the following environment variables must be set:
* `HASHICORP_CLIENT_KEYSTORE_PWD`
* `HASHICORP_CLIENT_TRUSTSTORE_PWD`
> If using a Hashicorp Vault additional environment variables must be set and a version 2 K/V secret engine must be enabled. For more information see [Setting up a Hashicorp Vault](../../Tessera%20Services/Keys/Setting%20up%20a%20Hashicorp%20Vault).
!!! info
If using a Hashicorp Vault additional environment variables must be set and a version 2 K/V secret engine must be enabled. For more information see [Setting up a Hashicorp Vault](../../Tessera%20Services/Keys/Setting%20up%20a%20Hashicorp%20Vault).
#### 5. Filesystem key pairs
The keys in the pair are stored in files:
```
"keys": {
"passwordFile": "/path/to/passwords",
"keyData": [
{
"privateKeyPath": "/path/to/privateKey.key",
"publicKeyPath": "/path/to/publicKey.pub"
}
]
}
```
The contents of the public key file must contain the public key only, e.g.:
```
/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=
```
The contents of the private key file must contain the private key in the config format, e.g.:
```
{
"type" : "unlocked",
"data" : {
"bytes" : "DK0HDgMWJKtZVaP31mPhk6TJNACfVzz7VZv2PsQZeKM="
}
}
```
## Multiple Keys
If wished, multiple key pairs can be specified for a Tessera node. In this case, any one of the public keys can be used to address a private transaction to that node. Tessera will sequentially try each key to find one that can decrypt the payload. This can be used, for example, to simplify key rotation.

9
docs/private-abigen.md Normal file
View File

@ -0,0 +1,9 @@
# Abigen with Quorum
### Overview
Abigen is a source code generator that converts smart contract ABI definitions into type-safe Go packages. In addition to the original capabilities provided by Ethereum described [here](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts). Quorum Abigen also supports private transactions.
### Implementation
`PrivateFrom` and `PrivateFor` fields have been added to the `bind.TransactOpts` which allows users to specify the public keys of the transaction manager (Tessera/Constellation) used to send and receive private transactions. The existing `ethclient` has been extended with a private transaction manager client to support sending `/storeraw` request.

View File

@ -258,6 +258,8 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo
config.Istanbul.Epoch = chainConfig.Istanbul.Epoch
}
config.Istanbul.ProposerPolicy = istanbul.ProposerPolicy(chainConfig.Istanbul.ProposerPolicy)
config.Istanbul.Ceil2Nby3Block = chainConfig.Istanbul.Ceil2Nby3Block
return istanbulBackend.New(&config.Istanbul, ctx.NodeKey(), db)
}

View File

@ -82,7 +82,7 @@ func TestNodeInfo(t *testing.T) {
}{
{"ethash", nil, nil, false},
{"raft", nil, nil, true},
{"istanbul", nil, &params.IstanbulConfig{1, 1}, false},
{"istanbul", nil, &params.IstanbulConfig{1, 1, big.NewInt(0)}, false},
{"clique", &params.CliqueConfig{1, 1}, nil, false},
}

View File

@ -24,6 +24,8 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@ -34,7 +36,8 @@ import (
// Client defines typed wrappers for the Ethereum RPC API.
type Client struct {
c *rpc.Client
c *rpc.Client
pc privateTransactionManagerClient // Tessera/Constellation client
}
// Dial connects a client to the given URL.
@ -52,7 +55,19 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
// NewClient creates a client that uses the given RPC client.
func NewClient(c *rpc.Client) *Client {
return &Client{c}
return &Client{c, nil}
}
// Quorum
//
// provides support for private transactions
func (ec *Client) WithPrivateTransactionManager(rawurl string) (*Client, error) {
var err error
ec.pc, err = newPrivateTransactionManagerClient(rawurl)
if err != nil {
return nil, err
}
return ec, nil
}
func (ec *Client) Close() {
@ -498,12 +513,26 @@ func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64
//
// If the transaction was a contract creation use the TransactionReceipt method to get the
// contract address after the transaction has been mined.
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction, args bind.PrivateTxArgs) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
if args.PrivateFor != nil {
return ec.c.CallContext(ctx, nil, "eth_sendRawPrivateTransaction", common.ToHex(data), bind.PrivateTxArgs{PrivateFor: args.PrivateFor})
} else {
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
}
}
// Quorum
//
// Retrieve encrypted payload hash from the private transaction manager if configured
func (ec *Client) PreparePrivateTransaction(data []byte, privateFrom string) ([]byte, error) {
if ec.pc == nil {
return nil, errors.New("missing private transaction manager client configuration")
}
return ec.pc.storeRaw(data, privateFrom)
}
func toCallArg(msg ethereum.CallMsg) interface{} {

View File

@ -22,6 +22,8 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
)
@ -150,3 +152,30 @@ func TestToFilterArg(t *testing.T) {
})
}
}
func TestClient_PreparePrivateTransaction_whenTypical(t *testing.T) {
testObject := NewClient(nil)
_, err := testObject.PreparePrivateTransaction([]byte("arbitrary payload"), "arbitrary private from")
assert.Error(t, err)
}
func TestClient_PreparePrivateTransaction_whenClientIsConfigured(t *testing.T) {
expectedData := []byte("arbitrary data")
testObject := NewClient(nil)
testObject.pc = &privateTransactionManagerStubClient{expectedData}
actualData, err := testObject.PreparePrivateTransaction([]byte("arbitrary payload"), "arbitrary private from")
assert.NoError(t, err)
assert.Equal(t, expectedData, actualData)
}
type privateTransactionManagerStubClient struct {
expectedData []byte
}
func (s *privateTransactionManagerStubClient) storeRaw(data []byte, privateFrom string) ([]byte, error) {
return s.expectedData, nil
}

View File

@ -0,0 +1,71 @@
package ethclient
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type privateTransactionManagerClient interface {
storeRaw(data []byte, privateFrom string) ([]byte, error)
}
type privateTransactionManagerDefaultClient struct {
rawurl string
httpClient *http.Client
}
// Create a new client to interact with private transaction manager via a HTTP endpoint
func newPrivateTransactionManagerClient(endpoint string) (privateTransactionManagerClient, error) {
_, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
return &privateTransactionManagerDefaultClient{
rawurl: endpoint,
httpClient: &http.Client{},
}, nil
}
type storeRawReq struct {
Payload string `json:"payload"`
From string `json:"from,omitempty"`
}
type storeRawResp struct {
Key string `json:"key"`
}
func (pc *privateTransactionManagerDefaultClient) storeRaw(data []byte, privateFrom string) ([]byte, error) {
storeRawReq := &storeRawReq{
Payload: base64.StdEncoding.EncodeToString(data),
From: privateFrom,
}
reqBodyBuf := new(bytes.Buffer)
if err := json.NewEncoder(reqBodyBuf).Encode(storeRawReq); err != nil {
return nil, err
}
resp, err := pc.httpClient.Post(pc.rawurl+"/storeraw", "application/json", reqBodyBuf)
if err != nil {
return nil, fmt.Errorf("unable to invoke /storeraw due to %s", err)
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returns %s", resp.Status)
}
// parse response
var storeRawResp storeRawResp
if err := json.NewDecoder(resp.Body).Decode(&storeRawResp); err != nil {
return nil, err
}
encryptedPayloadHash, err := base64.StdEncoding.DecodeString(storeRawResp.Key)
if err != nil {
return nil, err
}
return encryptedPayloadHash, nil
}

View File

@ -0,0 +1,53 @@
package ethclient
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
const (
arbitraryBase64Data = "YXJiaXRyYXJ5IGRhdGE=" // = "arbitrary data"
)
func TestPrivateTransactionManagerClient_storeRaw(t *testing.T) {
// mock tessera client
arbitraryServer := newStoreRawServer()
defer arbitraryServer.Close()
testObject, err := newPrivateTransactionManagerClient(arbitraryServer.URL)
assert.NoError(t, err)
key, err := testObject.storeRaw([]byte("arbitrary payload"), "arbitrary private from")
assert.NoError(t, err)
assert.Equal(t, "arbitrary data", string(key))
}
func newStoreRawServer() *httptest.Server {
arbitraryResponse := fmt.Sprintf(`
{
"key": "%s"
}
`, arbitraryBase64Data)
mux := http.NewServeMux()
mux.HandleFunc("/storeraw", func(w http.ResponseWriter, req *http.Request) {
if req.Method == "POST" {
// parse request
var storeRawReq storeRawReq
if err := json.NewDecoder(req.Body).Decode(&storeRawReq); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// send response
_, _ = fmt.Fprintf(w, "%s", arbitraryResponse)
} else {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
}
})
return httptest.NewServer(mux)
}

View File

@ -1593,7 +1593,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
}
newTx := sendArgs.toTransaction()
// set v param to 37 to indicate private tx before submitting to the signer.
if len(sendArgs.PrivateFor) > 0 {
if sendArgs.PrivateFor != nil {
newTx.SetPrivate()
}
signedTx, err := s.sign(sendArgs.From, newTx)

View File

@ -544,7 +544,10 @@ func (w *worker) taskLoop() {
w.pendingMu.Lock()
w.pendingTasks[w.engine.SealHash(task.block.Header())] = task
w.pendingMu.Unlock()
go w.seal(task.block, stopCh)
if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil {
log.Warn("Block sealing failed", "err", err)
}
case <-w.exitCh:
interrupt()
return
@ -552,12 +555,6 @@ func (w *worker) taskLoop() {
}
}
func (w *worker) seal(b *types.Block, stop <-chan struct{}) {
if err := w.engine.Seal(w.chain, b, w.resultCh, stop); err != nil {
log.Warn("Block sealing failed", "err", err)
}
}
// resultLoop is a standalone goroutine to handle sealing result submitting
// and flush relative data to the database.
func (w *worker) resultLoop() {
@ -585,39 +582,38 @@ func (w *worker) resultLoop() {
}
// Different block could share same sealhash, deep copy here to prevent write-write conflict.
var logs []*types.Log
work := w.current
for _, receipt := range append(work.receipts, work.privateReceipts...) {
for _, receipt := range append(task.receipts, task.privateReceipts...) {
// Update the block hash in all logs since it is now available and not when the
// receipt/log of individual transactions were created.
for _, log := range receipt.Logs {
log.BlockHash = hash
}
logs = append(logs, receipt.Logs...)
}
for _, log := range append(work.state.Logs(), work.privateState.Logs()...) {
log.BlockHash = hash
}
// write private transacions
privateStateRoot, _ := work.privateState.Commit(w.config.IsEIP158(block.Number()))
core.WritePrivateStateRoot(w.eth.ChainDb(), block.Root(), privateStateRoot)
allReceipts := mergeReceipts(work.receipts, work.privateReceipts)
// Commit block and state to database.
w.mu.Lock()
stat, err := w.chain.WriteBlockWithState(block, allReceipts, work.state, nil)
w.mu.Unlock()
// write private transactions
privateStateRoot, err := task.privateState.Commit(w.config.IsEIP158(block.Number()))
if err != nil {
log.Error("Failed writWriteBlockAndStating block to chain", "err", err)
log.Error("Failed committing private state root", "err", err)
continue
}
if err := core.WritePrivateStateRoot(w.eth.ChainDb(), block.Root(), privateStateRoot); err != nil {
log.Error("Failed writing private state root", "err", err)
continue
}
allReceipts := mergeReceipts(task.receipts, task.privateReceipts)
if err := core.WritePrivateBlockBloom(w.eth.ChainDb(), block.NumberU64(), work.privateReceipts); err != nil {
// Commit block and state to database.
stat, err := w.chain.WriteBlockWithState(block, allReceipts, task.state, nil)
if err != nil {
log.Error("Failed writing block to chain", "err", err)
continue
}
if err := core.WritePrivateBlockBloom(w.eth.ChainDb(), block.NumberU64(), task.privateReceipts); err != nil {
log.Error("Failed writing private block bloom", "err", err)
continue
}
log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash,
"elapsed", common.PrettyDuration(time.Since(task.createdAt)))
@ -625,8 +621,6 @@ func (w *worker) resultLoop() {
w.mux.Post(core.NewMinedBlockEvent{Block: block})
var events []interface{}
logs = append(work.state.Logs(), work.privateState.Logs()...)
switch stat {
case core.CanonStatTy:
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
@ -1023,8 +1017,8 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
privateReceipts := make([]*types.Receipt, len(w.current.privateReceipts))
for i, l := range w.current.privateReceipts {
receipts[i] = new(types.Receipt)
*receipts[i] = *l
privateReceipts[i] = new(types.Receipt)
*privateReceipts[i] = *l
}
s := w.current.state.Copy()

View File

@ -21,6 +21,8 @@ package geth
import (
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
@ -312,5 +314,5 @@ func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (gas int64, _
// If the transaction was a contract creation use the TransactionReceipt method to get the
// contract address after the transaction has been mined.
func (ec *EthereumClient) SendTransaction(ctx *Context, tx *Transaction) error {
return ec.client.SendTransaction(ctx.context, tx.tx)
return ec.client.SendTransaction(ctx.context, tx.tx, bind.PrivateTxArgs{})
}

View File

@ -124,6 +124,7 @@ var (
Istanbul: &IstanbulConfig{
Epoch: 30000,
ProposerPolicy: 0,
Ceil2Nby3Block: big.NewInt(0),
},
}
@ -218,8 +219,9 @@ func (c *CliqueConfig) String() string {
// IstanbulConfig is the consensus engine configs for Istanbul based sealing.
type IstanbulConfig struct {
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
ProposerPolicy uint64 `json:"policy"` // The policy for proposer selection
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
ProposerPolicy uint64 `json:"policy"` // The policy for proposer selection
Ceil2Nby3Block *big.Int `json:"ceil2Nby3Block,omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)]
}
// String implements the stringer interface, returning the consensus engine details.
@ -386,6 +388,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int, isQuor
if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
}
if c.Istanbul != nil && newcfg.Istanbul != nil && isForkIncompatible(c.Istanbul.Ceil2Nby3Block, newcfg.Istanbul.Ceil2Nby3Block, head) {
return newCompatError("Ceil 2N/3 fork block", c.Istanbul.Ceil2Nby3Block, newcfg.Istanbul.Ceil2Nby3Block)
}
if isForkIncompatible(c.QIP714Block, newcfg.QIP714Block, head) {
return newCompatError("permissions fork block", c.QIP714Block, newcfg.QIP714Block)
}

View File

@ -70,6 +70,23 @@ func TestCheckCompatible(t *testing.T) {
RewindTo: 9,
},
},
{
stored: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(10)}},
new: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(20)}},
head: 4,
wantErr: nil,
},
{
stored: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(10)}},
new: &ChainConfig{Istanbul: &IstanbulConfig{Ceil2Nby3Block: big.NewInt(20)}},
head: 30,
wantErr: &ConfigCompatError{
What: "Ceil 2N/3 fork block",
StoredConfig: big.NewInt(10),
NewConfig: big.NewInt(20),
RewindTo: 9,
},
},
}
for _, test := range tests {

View File

@ -865,10 +865,6 @@ func (pm *ProtocolManager) makeInitialRaftPeers() (raftPeers []etcdRaft.Peer, pe
return
}
func sleep(duration time.Duration) {
<-time.NewTimer(duration).C
}
func blockExtendsChain(block *types.Block, chain *core.BlockChain) bool {
return block.ParentHash() == chain.CurrentBlock().Hash()
}