diff --git a/core/types/block.go b/core/types/block.go index 92b868d9da..fc3da50d5c 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -100,6 +100,14 @@ type headerMarshaling struct { // Hash returns the block hash of the header, which is simply the keccak256 hash of its // RLP encoding. func (h *Header) Hash() common.Hash { + // If the mix digest is equivalent to the predefined Istanbul digest, use Istanbul + // specific hash calculation. + if h.MixDigest == IstanbulDigest { + // Seal is reserved in extra-data. To prove block is signed by the proposer. + if istanbulHeader := IstanbulFilteredHeader(h, true); istanbulHeader != nil { + return rlpHash(istanbulHeader) + } + } return rlpHash(h) } diff --git a/core/types/istanbul.go b/core/types/istanbul.go new file mode 100644 index 0000000000..3375dbbda3 --- /dev/null +++ b/core/types/istanbul.go @@ -0,0 +1,107 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "errors" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + // IstanbulDigest represents a hash of "Istanbul practical byzantine fault tolerance" + // to identify whether the block is from Istanbul consensus engine + IstanbulDigest = common.HexToHash("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365") + + IstanbulExtraVanity = 32 // Fixed number of extra-data bytes reserved for validator vanity + IstanbulExtraSeal = 65 // Fixed number of extra-data bytes reserved for validator seal + + // ErrInvalidIstanbulHeaderExtra is returned if the length of extra-data is less than 32 bytes + ErrInvalidIstanbulHeaderExtra = errors.New("invalid istanbul header extra-data") +) + +type IstanbulExtra struct { + Validators []common.Address + Seal []byte + CommittedSeal [][]byte +} + +// EncodeRLP serializes ist into the Ethereum RLP format. +func (ist *IstanbulExtra) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{ + ist.Validators, + ist.Seal, + ist.CommittedSeal, + }) +} + +// DecodeRLP implements rlp.Decoder, and load the istanbul fields from a RLP stream. +func (ist *IstanbulExtra) DecodeRLP(s *rlp.Stream) error { + var istanbulExtra struct { + Validators []common.Address + Seal []byte + CommittedSeal [][]byte + } + if err := s.Decode(&istanbulExtra); err != nil { + return err + } + ist.Validators, ist.Seal, ist.CommittedSeal = istanbulExtra.Validators, istanbulExtra.Seal, istanbulExtra.CommittedSeal + return nil +} + +// ExtractIstanbulExtra extracts all values of the IstanbulExtra from the header. It returns an +// error if the length of the given extra-data is less than 32 bytes or the extra-data can not +// be decoded. +func ExtractIstanbulExtra(h *Header) (*IstanbulExtra, error) { + if len(h.Extra) < IstanbulExtraVanity { + return nil, ErrInvalidIstanbulHeaderExtra + } + + var istanbulExtra *IstanbulExtra + err := rlp.DecodeBytes(h.Extra[IstanbulExtraVanity:], &istanbulExtra) + if err != nil { + return nil, err + } + return istanbulExtra, nil +} + +// IstanbulFilteredHeader returns a filtered header which some information (like seal, committed seals) +// are clean to fulfill the Istanbul hash rules. It returns nil if the extra-data cannot be +// decoded/encoded by rlp. +func IstanbulFilteredHeader(h *Header, keepSeal bool) *Header { + newHeader := CopyHeader(h) + istanbulExtra, err := ExtractIstanbulExtra(newHeader) + if err != nil { + return nil + } + + if !keepSeal { + istanbulExtra.Seal = []byte{} + } + istanbulExtra.CommittedSeal = [][]byte{} + + payload, err := rlp.EncodeToBytes(&istanbulExtra) + if err != nil { + return nil + } + + newHeader.Extra = append(newHeader.Extra[:IstanbulExtraVanity], payload...) + + return newHeader +} diff --git a/core/types/istanbul_test.go b/core/types/istanbul_test.go new file mode 100644 index 0000000000..db5453b53a --- /dev/null +++ b/core/types/istanbul_test.go @@ -0,0 +1,88 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func TestHeaderHash(t *testing.T) { + // 0xcefefd3ade63a5955bca4562ed840b67f39e74df217f7e5f7241a6e9552cca70 + expectedExtra := common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000000f89af8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b440b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0") + expectedHash := common.HexToHash("0xcefefd3ade63a5955bca4562ed840b67f39e74df217f7e5f7241a6e9552cca70") + + // for istanbul consensus + header := &Header{MixDigest: IstanbulDigest, Extra: expectedExtra} + if !reflect.DeepEqual(header.Hash(), expectedHash) { + t.Errorf("expected: %v, but got: %v", expectedHash.Hex(), header.Hash().Hex()) + } + + // append useless information to extra-data + unexpectedExtra := append(expectedExtra, []byte{1, 2, 3}...) + header.Extra = unexpectedExtra + if !reflect.DeepEqual(header.Hash(), rlpHash(header)) { + t.Errorf("expected: %v, but got: %v", rlpHash(header).Hex(), header.Hash().Hex()) + } +} + +func TestExtractToIstanbul(t *testing.T) { + testCases := []struct { + vanity []byte + istRawData []byte + expectedResult *IstanbulExtra + expectedErr error + }{ + { + // normal case + bytes.Repeat([]byte{0x00}, IstanbulExtraVanity), + hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0"), + &IstanbulExtra{ + Validators: []common.Address{ + common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")), + common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")), + common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")), + common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")), + }, + Seal: []byte{}, + CommittedSeal: [][]byte{}, + }, + nil, + }, + { + // insufficient vanity + bytes.Repeat([]byte{0x00}, IstanbulExtraVanity-1), + nil, + nil, + ErrInvalidIstanbulHeaderExtra, + }, + } + for _, test := range testCases { + h := &Header{Extra: append(test.vanity, test.istRawData...)} + istanbulExtra, err := ExtractIstanbulExtra(h) + if err != test.expectedErr { + t.Errorf("expected: %v, but got: %v", test.expectedErr, err) + } + if !reflect.DeepEqual(istanbulExtra, test.expectedResult) { + t.Errorf("expected: %v, but got: %v", test.expectedResult, istanbulExtra) + } + } +}