fix impossible reorg

This commit is contained in:
Trung Nguyen 2018-10-05 16:17:46 -04:00
commit 4e22d13b10
No known key found for this signature in database
GPG Key ID: 4636434ED9505EB7
16 changed files with 348 additions and 54 deletions

View File

@ -26,9 +26,12 @@ The above diagram is a high-level overview of the privacy architecture used by Q
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).
## Official Docker Containers
The official docker containers can be found under https://hub.docker.com/u/quorumengineering/
## See also
* [Quorum](https://github.com/jpmorganchase/quorum): this repository

View File

@ -264,3 +264,17 @@ func RegisterRaftService(stack *node.Node, ctx *cli.Context, cfg gethConfig, eth
}
}
// quorumValidateConsensus checks if a consensus was used. The node is killed if consensus was not used
func quorumValidateConsensus(stack *node.Node, isRaft bool) {
var ethereum *eth.Ethereum
err := stack.Service(&ethereum)
if err != nil {
utils.Fatalf("Error retrieving Ethereum service: %v", err)
}
if !isRaft && ethereum.ChainConfig().Istanbul == nil && ethereum.ChainConfig().Clique == nil {
utils.Fatalf("Consensus not specified. Exiting!!")
}
}

View File

@ -18,6 +18,7 @@ package main
import (
"crypto/rand"
"io/ioutil"
"math/big"
"os"
"path/filepath"
@ -31,18 +32,52 @@ import (
)
const (
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
nodeKey = "b68c0338aa4b266bf38ebe84c6199ae9fac8b29f32998b3ed2fbeafebe8d65c9"
)
var genesis = `{
"config": {
"chainId": 2017,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 3,
"eip158Block": 3,
"istanbul": {
"epoch": 30000,
"policy": 0
}
},
"nonce": "0x0",
"timestamp": "0x0",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"491937757d1b26e29c507b8d4c0b233c2747e68d": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
`
// Tests that a node embedded within a console can be started up properly and
// then terminated by closing the input stream.
func TestConsoleWelcome(t *testing.T) {
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
// Start a geth console, make sure it's cleaned up and terminate the console
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--shh",
"console")
@ -72,19 +107,22 @@ at block: 0 ({{niltime}})
// Tests that a console can be attached to a running node via various means.
func TestIPCAttachWelcome(t *testing.T) {
// Configure the instance for IPC attachement
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
var ipc string
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
if runtime.GOOS == "windows" {
ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999))
} else {
ws := tmpdir(t)
defer os.RemoveAll(ws)
ipc = filepath.Join(ws, "geth.ipc")
ipc = filepath.Join(datadir, "geth.ipc")
}
// Note: we need --shh because testAttachWelcome checks for default
// list of ipc modules and shh is included there.
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
@ -95,10 +133,14 @@ func TestIPCAttachWelcome(t *testing.T) {
}
func TestHTTPAttachWelcome(t *testing.T) {
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--rpc", "--rpcport", port)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
@ -109,11 +151,14 @@ func TestHTTPAttachWelcome(t *testing.T) {
}
func TestWSAttachWelcome(t *testing.T) {
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
coinbase := "0x491937757d1b26e29c507b8d4c0b233c2747e68d"
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
datadir := setupIstanbul(t)
defer os.RemoveAll(datadir)
geth := runGeth(t,
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
"--etherbase", coinbase, "--ws", "--wsport", port)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
@ -161,3 +206,26 @@ func trulyRandInt(lo, hi int) int {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(hi-lo)))
return int(num.Int64()) + lo
}
// setupIstanbul creates a temporary directory and copies nodekey and genesis.json.
// It initializes istanbul by calling geth init
func setupIstanbul(t *testing.T) string {
datadir := tmpdir(t)
gethPath := filepath.Join(datadir, "geth")
os.Mkdir(gethPath, 0700)
// Initialize the data directory with the custom genesis block
json := filepath.Join(datadir, "genesis.json")
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
t.Fatalf("failed to write genesis file: %v", err)
}
nodeKeyFile := filepath.Join(gethPath, "nodekey")
if err := ioutil.WriteFile(nodeKeyFile, []byte(nodeKey), 0600); err != nil {
t.Fatalf("failed to write nodekey file: %v", err)
}
runGeth(t, "--datadir", datadir, "init", json).WaitExit()
return datadir
}

View File

@ -216,6 +216,10 @@ func main() {
func geth(ctx *cli.Context) error {
node := makeFullNode(ctx)
startNode(ctx, node)
// Check if a valid consensus is used
quorumValidateConsensus(node, ctx.GlobalBool(utils.RaftModeFlag.Name))
node.Wait()
return nil
}

View File

@ -23,7 +23,7 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/istanbul"
"github.com/ethereum/go-ethereum/p2p"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/golang-lru"
)
const (
@ -44,6 +44,15 @@ func (sb *backend) Protocol() consensus.Protocol {
}
}
func (sb *backend) decode(msg p2p.Msg) ([]byte, common.Hash, error) {
var data []byte
if err := msg.Decode(&data); err != nil {
return nil, common.Hash{}, errDecodeFailed
}
return data, istanbul.RLPHash(data), nil
}
// HandleMsg implements consensus.Handler.HandleMsg
func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
sb.coreMu.Lock()
@ -54,13 +63,11 @@ func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
return true, istanbul.ErrStoppedEngine
}
var data []byte
if err := msg.Decode(&data); err != nil {
data, hash, err := sb.decode(msg)
if err != nil {
return true, errDecodeFailed
}
hash := istanbul.RLPHash(data)
// Mark peer's message
ms, ok := sb.recentMessages.Get(addr)
var m *lru.ARCCache
@ -84,6 +91,17 @@ func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
return true, nil
}
if msg.Code == 0x07 && sb.core.IsProposer() { // eth.NewBlockMsg: import cycle
// this case is to safeguard the race of similar block which gets propagated from other node while this node is proposing
_, hash, err := sb.decode(msg)
if err != nil {
return true, errDecodeFailed
}
if _, ok := sb.knownMessages.Get(hash); ok {
return true, nil
}
}
return false, nil
}

View File

@ -152,7 +152,7 @@ func (c *core) currentView() *istanbul.View {
}
}
func (c *core) isProposer() bool {
func (c *core) IsProposer() bool {
v := c.valSet
if v == nil {
return false
@ -240,7 +240,7 @@ func (c *core) startNewRound(round *big.Int) {
c.valSet.CalcProposer(lastProposer, newView.Round.Uint64())
c.waitingForRoundChange = false
c.setState(StateAcceptRequest)
if roundChange && c.isProposer() && c.current != nil {
if roundChange && c.IsProposer() && c.current != nil {
// If it is locked, propose the old proposal
// If we have pending request, propose pending request
if c.current.IsHashLocked() {
@ -254,7 +254,7 @@ func (c *core) startNewRound(round *big.Int) {
}
c.newRoundChangeTimer()
logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "isProposer", c.isProposer())
logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "IsProposer", c.IsProposer())
}
func (c *core) catchUpRound(view *istanbul.View) {

View File

@ -27,7 +27,7 @@ func (c *core) sendPreprepare(request *istanbul.Request) {
logger := c.logger.New("state", c.state)
// If I'm the proposer and I have the same sequence with the proposal
if c.current.Sequence().Cmp(request.Proposal.Number()) == 0 && c.isProposer() {
if c.current.Sequence().Cmp(request.Proposal.Number()) == 0 && c.IsProposer() {
curView := c.currentView()
preprepare, err := Encode(&istanbul.Preprepare{
View: curView,

View File

@ -27,6 +27,7 @@ import (
type Engine interface {
Start() error
Stop() error
IsProposer() bool
}
type State uint64

View File

@ -585,8 +585,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
return ErrNonceTooLow
}
// Ether value is not currently supported on private transactions
if tx.IsPrivate() && (tx.Value().Sign() != 0) {
return ErrEtherValueUnsupported;
if tx.IsPrivate() && (len(tx.Data()) == 0 || tx.Value().Sign() != 0) {
return ErrEtherValueUnsupported
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL

View File

@ -19,8 +19,14 @@ package core
import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"math/big"
"math/rand"
"os"
"reflect"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@ -28,11 +34,6 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"math/rand"
"time"
"io/ioutil"
"os"
"reflect"
)
// testTxPoolConfig is a transaction pool configuration without stateful disk
@ -281,6 +282,54 @@ func TestQuorumInvalidTransactions(t *testing.T) {
}
func TestValidateTx_whenValueZeroTransferForPrivateTransaction(t *testing.T) {
pool, key := setupQuorumTxPool()
defer pool.Stop()
zeroValue := common.Big0
zeroGasPrice := common.Big0
defaultTxPoolGasLimit := big.NewInt(1000000)
arbitraryTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, zeroValue, defaultTxPoolGasLimit, zeroGasPrice, nil), types.HomesteadSigner{}, key)
arbitraryTx.SetPrivate()
if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported {
t.Error("expected:", ErrEtherValueUnsupported, "; got:", err)
}
}
func TestValidateTx_whenValueNonZeroTransferForPrivateTransaction(t *testing.T) {
pool, key := setupQuorumTxPool()
defer pool.Stop()
arbitraryValue := common.Big3
arbitraryTx, balance, from := newPrivateTransaction(arbitraryValue, nil, key)
pool.currentState.AddBalance(from, balance)
if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported {
t.Error("expected: ", ErrEtherValueUnsupported, "; got:", err)
}
}
func newPrivateTransaction(value *big.Int, data []byte, key *ecdsa.PrivateKey) (*types.Transaction, *big.Int, common.Address) {
zeroGasPrice := common.Big0
defaultTxPoolGasLimit := big.NewInt(1000000)
arbitraryTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, value, defaultTxPoolGasLimit, zeroGasPrice, data), types.HomesteadSigner{}, key)
arbitraryTx.SetPrivate()
balance := new(big.Int).Add(arbitraryTx.Value(), new(big.Int).Mul(arbitraryTx.Gas(), arbitraryTx.GasPrice()))
from, _ := deriveSender(arbitraryTx)
return arbitraryTx, balance, from
}
func TestValidateTx_whenValueNonZeroWithSmartContractForPrivateTransaction(t *testing.T) {
pool, key := setupQuorumTxPool()
defer pool.Stop()
arbitraryValue := common.Big3
arbitraryTx, balance, from := newPrivateTransaction(arbitraryValue, []byte("arbitrary bytecode"), key)
pool.currentState.AddBalance(from, balance)
if err := pool.AddRemote(arbitraryTx); err != ErrEtherValueUnsupported {
t.Error("expected: ", ErrEtherValueUnsupported, "; got:", err)
}
}
func TestTransactionQueue(t *testing.T) {
pool, key := setupTxPool()
defer pool.Stop()
@ -1527,9 +1576,9 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) {
//Checks that the EIP155 signer is assigned to the TxPool no matter the configuration, even invalid config
func TestEIP155SignerOnTxPool(t *testing.T) {
var flagtests = []struct {
name string
homesteadBlock *big.Int
eip155Block *big.Int
name string
homesteadBlock *big.Int
eip155Block *big.Int
}{
{"hsnileip155nil", nil, nil},
{"hsnileip1550", nil, big.NewInt(0)},
@ -1567,4 +1616,3 @@ func TestEIP155SignerOnTxPool(t *testing.T) {
}
}

View File

@ -362,12 +362,14 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
data := []byte(args.Data)
isPrivate := args.PrivateFor != nil
if isPrivate {
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
if len(data) > 0 {
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
} // else tx_pool.go#validateTx will capture and throw error
args.Data = data
}
@ -1152,13 +1154,15 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
isPrivate := args.PrivateFor != nil
if isPrivate {
//Send private transaction to local Constellation node
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
if len(data) > 0 {
//Send private transaction to local Constellation node
log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
if err != nil {
return common.Hash{}, err
}
} // else tx_pool.go#validateTx will capture and throw error
args.Data = data
}
@ -1239,6 +1243,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
if err != nil {
return nil, err
}
if args.PrivateFor != nil {
tx.SetPrivate()
}
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return nil, err

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
@ -15,7 +16,7 @@ import (
const (
timeFormat = "2006-01-02T15:04:05-0700"
termTimeFormat = "01-02|15:04:05"
termTimeFormat = "01-02|15:04:05.000"
floatFormat = 'f'
termMsgJust = 40
)
@ -107,7 +108,7 @@ func TerminalFormat(usecolor bool) Format {
lvl := r.Lvl.AlignedString()
if atomic.LoadUint32(&locationEnabled) != 0 {
// Log origin printing was requested, format the location path and line number
location := fmt.Sprintf("%+v", r.Call)
location := fmt.Sprintf("%+v|%v", r.Call, getGID())
for _, prefix := range locationTrims {
location = strings.TrimPrefix(location, prefix)
}
@ -361,3 +362,12 @@ func escapeString(s string) string {
stringBufPool.Put(e)
return ret
}
func getGID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
}

View File

@ -145,7 +145,7 @@ var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
//
// For complete nodes, the node ID is encoded in the username portion
// of the URL, separated from the host by an @ sign. The hostname can
// only be given as an IP address, DNS domain names are not allowed.
// be given as an IP address or a DNS domain name.
// The port in the host name section is the TCP listening port. If the
// TCP and UDP (discovery) ports differ, the UDP port is specified as
// query parameter "discport".
@ -192,7 +192,13 @@ func parseComplete(rawurl string) (*Node, error) {
return nil, fmt.Errorf("invalid host: %v", err)
}
if ip = net.ParseIP(host); ip == nil {
return nil, errors.New("invalid IP address")
// attempt to look up IP addresses if host is a FQDN
lookupIPs, err := net.LookupIP(host)
if err != nil {
return nil, errors.New("invalid IP address")
}
// set to first ip by default
ip = lookupIPs[0]
}
// Ensure the IP is 4 bytes long for IPv4 addresses.
if ipv4 := ip.To4(); ipv4 != nil {

View File

@ -3,6 +3,7 @@ package raft
import (
"sync"
"time"
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core"
@ -32,6 +33,7 @@ type RaftService struct {
// we need an event mux to instantiate the blockchain
eventMux *event.TypeMux
minter *minter
nodeKey *ecdsa.PrivateKey
}
func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raftPort uint16, joinExisting bool, blockTime time.Duration, e *eth.Ethereum, startPeers []*discover.Node, datadir string) (*RaftService, error) {
@ -43,6 +45,7 @@ func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raft
accountManager: e.AccountManager(),
downloader: e.Downloader(),
startPeers: startPeers,
nodeKey: ctx.NodeKey(),
}
service.minter = newMinter(chainConfig, service, blockTime)

View File

@ -25,16 +25,22 @@ import (
"github.com/eapache/channels"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
var (
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for arbitrary signer vanity
)
// Current state information for building the next block
@ -50,7 +56,7 @@ type minter struct {
config *params.ChainConfig
mu sync.Mutex
mux *event.TypeMux
eth miner.Backend
eth *RaftService
chain *core.BlockChain
chainDb ethdb.Database
coinbase common.Address
@ -66,6 +72,11 @@ type minter struct {
txPreSub event.Subscription
}
type extraSeal struct {
RaftId []byte // RaftID of the block minter
Signature []byte // Signature of the block minter
}
func newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Duration) *minter {
minter := &minter{
config: config,
@ -318,8 +329,6 @@ func (minter *minter) mintNewBlock() {
ethash.AccumulateRewards(minter.chain.Config(), work.publicState, header, nil)
header.Root = work.publicState.IntermediateRoot(minter.chain.Config().IsEIP158(work.header.Number))
// NOTE: < QuorumChain creates a signature here and puts it in header.Extra. >
allReceipts := append(publicReceipts, privateReceipts...)
header.Bloom = types.CreateBloom(allReceipts)
@ -330,6 +339,14 @@ func (minter *minter) mintNewBlock() {
l.BlockHash = headerHash
}
//Sign the block and build the extraSeal struct
extraSealBytes := minter.buildExtraSeal(headerHash)
// add vanity and seal to header
// NOTE: leaving vanity blank for now as a space for any future data
header.Extra = make([]byte, extraVanity+len(extraSealBytes))
copy(header.Extra[extraVanity:], extraSealBytes)
block := types.NewBlock(header, committedTxes, nil, publicReceipts)
log.Info("Generated next block", "block num", block.Number(), "num txes", txCount)
@ -407,3 +424,29 @@ func (env *work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, g
return publicReceipt, privateReceipt, nil
}
func (minter *minter) buildExtraSeal(headerHash common.Hash) []byte {
//Sign the headerHash
nodeKey := minter.eth.nodeKey
sig, err := crypto.Sign(headerHash.Bytes(), nodeKey)
if err != nil {
log.Warn("Block sealing failed", "err", err)
}
//build the extraSeal struct
raftIdString := hexutil.EncodeUint64(uint64(minter.eth.raftProtocolManager.raftId))
var extra extraSeal
extra = extraSeal{
RaftId: []byte(raftIdString[2:]), //remove the 0x prefix
Signature: sig,
}
//encode to byte array for storage
extraDataBytes, err := rlp.EncodeToBytes(extra)
if err != nil {
log.Warn("Header.Extra Data Encoding failed", "err", err)
}
return extraDataBytes
}

69
raft/minter_test.go Normal file
View File

@ -0,0 +1,69 @@
package raft
import (
"testing"
"math/big"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func TestSignHeader(t *testing.T){
//create only what we need to test the seal
var testRaftId uint16 = 5
config := &node.Config{Name: "unit-test", DataDir: ""}
nodeKey := config.NodeKey()
raftProtocolManager := &ProtocolManager{raftId:testRaftId}
raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager}
minter := minter{eth: raftService,}
//create some fake header to sign
fakeParentHash := common.HexToHash("0xc2c1dc1be8054808c69e06137429899d")
header := &types.Header{
ParentHash: fakeParentHash,
Number: big.NewInt(1),
Difficulty: big.NewInt(1),
GasLimit: new(big.Int),
GasUsed: new(big.Int),
Coinbase: minter.coinbase,
Time: big.NewInt(time.Now().UnixNano()),
}
headerHash := header.Hash()
extraDataBytes := minter.buildExtraSeal(headerHash)
var seal *extraSeal
err := rlp.DecodeBytes(extraDataBytes[:], &seal)
if err != nil {
t.Fatalf("Unable to decode seal: %s", err.Error())
}
// Check raftId
sealRaftId, err := hexutil.DecodeUint64("0x"+ string(seal.RaftId)) //add the 0x prefix
if err != nil {
t.Errorf("Unable to get RaftId: %s", err.Error())
}
if sealRaftId != uint64(testRaftId) {
t.Errorf("RaftID does not match. Expected: %d, Actual: %d", testRaftId, sealRaftId)
}
//Identify who signed it
sig:= seal.Signature
pubKey, err := crypto.SigToPub(headerHash.Bytes(), sig)
if err != nil {
t.Fatalf("Unable to get public key from signature: %s", err.Error())
}
//Compare derived public key to original public key
if pubKey.X.Cmp(nodeKey.X) != 0 {
t.Errorf("Signature incorrect!")
}
}