Skip to content

Commit 152b6dc

Browse files
mcortesihbanduragastonponti
authored
Reduce Preprepare size (#1924)
* Log line from Warn to Debug * Bump version to 1.5.8 * New encoding for RoundChangeCertificate * clean up NewIndexedRoundChangeMessage * Use pointers whenever possible to reduce call by value costs * Merge * Fix compile error * Added comment * Added docs * Add error checking * Add error * Clean up encoding logic * more cleanup * Fix compile errors * Added doc * Fix dummy object in test * Fix * Fix tests * Remove nil * fix proposal not rlp serializable * fix test * Add tests * Fix lint * Fix test * fix test * Fix test * Fix * Add log * Revert change * cleanup (#1925) * cleanup * revert * Fix debug log Co-authored-by: Pasto <[email protected]> Co-authored-by: Gaston Ponti <[email protected]>
1 parent 1e2a6a7 commit 152b6dc

File tree

5 files changed

+229
-19
lines changed

5 files changed

+229
-19
lines changed

Diff for: consensus/istanbul/types.go

+166-15
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/celo-org/celo-blockchain/core/types"
2929
"github.com/celo-org/celo-blockchain/crypto"
3030
blscrypto "github.com/celo-org/celo-blockchain/crypto/bls"
31+
"github.com/celo-org/celo-blockchain/log"
3132
"github.com/celo-org/celo-blockchain/p2p/enode"
3233
"github.com/celo-org/celo-blockchain/rlp"
3334
)
@@ -126,6 +127,10 @@ func (v *View) Cmp(y *View) int {
126127
}
127128

128129
// ## RoundChangeCertificate ##############################################################
130+
// To considerably reduce the bandwidth used by the RoundChangeCertificate type (which often
131+
// contains repeated Proposal from different RoundChange messages), we break it apart during
132+
// RLP encoding and then build it back during decoding. Proposals are sent just once, and
133+
// Messages referencing them will use their Hash instead.
129134

130135
type RoundChangeCertificate struct {
131136
RoundChangeMessages []Message
@@ -135,6 +140,134 @@ func (b *RoundChangeCertificate) IsEmpty() bool {
135140
return len(b.RoundChangeMessages) == 0
136141
}
137142

143+
// EncodeRLP serializes b into the Ethereum RLP format.
144+
func (c *RoundChangeCertificate) EncodeRLP(w io.Writer) error {
145+
proposals, messages, err := c.asValues()
146+
if err != nil {
147+
return err
148+
}
149+
log.Debug("Round change certificate proposals", "count", len(proposals))
150+
return rlp.Encode(w, []interface{}{proposals, messages})
151+
}
152+
153+
// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream.
154+
func (c *RoundChangeCertificate) DecodeRLP(s *rlp.Stream) error {
155+
var decodestr struct {
156+
Proposals []*types.Block
157+
IndexedMessages []IndexedRoundChangeMessage
158+
}
159+
160+
if err := s.Decode(&decodestr); err != nil {
161+
return err
162+
}
163+
return c.setValues(decodestr.Proposals, decodestr.IndexedMessages)
164+
}
165+
166+
// setValues recreates the RoundChange messages from the props (Proposal set/index) and the
167+
// list of IndexedRoundChangeMessage, which is supposed to be the same as the RoundChange
168+
// Messages but with the proposals just referenced to the Proposals set.
169+
func (c *RoundChangeCertificate) setValues(props []*types.Block, iMess []IndexedRoundChangeMessage) error {
170+
// create a Proposal index from the list
171+
propIndex := make(map[common.Hash]Proposal)
172+
for _, prop := range props {
173+
propIndex[prop.Hash()] = prop
174+
}
175+
// Recreate Messages one by one
176+
mess := make([]Message, len(iMess))
177+
for i, im := range iMess {
178+
mess[i] = Message{
179+
Code: im.Message.Code,
180+
Address: im.Message.Address,
181+
Signature: im.Message.Signature,
182+
}
183+
184+
// Add the proposal to the message if it had one
185+
roundChange, err := im.Message.TryRoundChange()
186+
if err != nil {
187+
return err
188+
}
189+
190+
if proposal, ok := propIndex[im.ProposalHash]; ok {
191+
roundChange.PreparedCertificate.Proposal = proposal
192+
}
193+
194+
setMessageBytes(&mess[i], roundChange)
195+
mess[i].roundChange = roundChange
196+
}
197+
c.RoundChangeMessages = mess
198+
return nil
199+
}
200+
201+
type IndexedRoundChangeMessage struct {
202+
ProposalHash common.Hash
203+
Message Message // PreparedCertificate.Proposal = nil if any
204+
}
205+
206+
// asValues presents the RoundChangeCertificate as values for RLP Serialization.
207+
// This is done using a list of proposals, and the RoundChange messages using
208+
// hash references instead of the full proposal objects, to reduce bandwidth.
209+
func (c *RoundChangeCertificate) asValues() ([]*types.Block, []*IndexedRoundChangeMessage, error) {
210+
var err error
211+
212+
messages := make([]*IndexedRoundChangeMessage, len(c.RoundChangeMessages))
213+
proposalsMap := make(map[common.Hash]*types.Block)
214+
215+
for i, message := range c.RoundChangeMessages {
216+
var proposal *types.Block
217+
proposal, messages[i], err = extractProposal(&message)
218+
if err != nil {
219+
return nil, nil, err
220+
}
221+
222+
if proposal != nil {
223+
// we don't use the height since we know they MUST be the same
224+
proposalsMap[proposal.Hash()] = proposal
225+
}
226+
}
227+
228+
// Iterate values. RLP does not support maps
229+
proposals := make([]*types.Block, len(proposalsMap))
230+
var i = 0
231+
for _, p := range proposalsMap {
232+
proposals[i] = p
233+
i++
234+
}
235+
return proposals, messages, nil
236+
}
237+
238+
func extractProposal(message *Message) (*types.Block, *IndexedRoundChangeMessage, error) {
239+
roundChange, err := message.TryRoundChange()
240+
if err != nil {
241+
return nil, nil, err
242+
}
243+
244+
pc := roundChange.PreparedCertificate
245+
246+
// Assume message.Code = MsgRoundChange
247+
indexedMsg := IndexedRoundChangeMessage{
248+
Message: Message{
249+
Code: message.Code,
250+
Address: message.Address,
251+
Signature: message.Signature,
252+
},
253+
}
254+
255+
if pc.Proposal != nil {
256+
indexedMsg.ProposalHash = pc.Proposal.Hash()
257+
}
258+
259+
curatedPC := EmptyPreparedCertificate()
260+
curatedPC.PrepareOrCommitMessages = pc.PrepareOrCommitMessages
261+
262+
setMessageBytes(&indexedMsg.Message,
263+
&RoundChange{
264+
View: roundChange.View,
265+
PreparedCertificate: curatedPC,
266+
})
267+
268+
return pc.Proposal.(*types.Block), &indexedMsg, nil
269+
}
270+
138271
// ## Preprepare ##############################################################
139272

140273
// NewPreprepareMessage constructs a Message instance with the given sender and
@@ -522,7 +655,7 @@ type Message struct {
522655
func setMessageBytes(msg *Message, innerMessage interface{}) {
523656
bytes, err := rlp.EncodeToBytes(innerMessage)
524657
if err != nil {
525-
panic(fmt.Sprintf("attempt to serialise inner message of type %T failed", innerMessage))
658+
panic(fmt.Sprintf("attempt to serialise inner message of type %T failed. %s", innerMessage, err))
526659
}
527660
msg.Msg = bytes
528661
}
@@ -537,20 +670,8 @@ func (m *Message) Sign(signingFn func(data []byte) ([]byte, error)) error {
537670
return err
538671
}
539672

540-
func (m *Message) DecodeRLP(stream *rlp.Stream) error {
541-
type decodable Message
542-
var d decodable
543-
err := stream.Decode(&d)
544-
if err != nil {
545-
return err
546-
}
547-
*m = Message(d)
548-
549-
if len(m.Msg) == 0 && len(m.Signature) == 0 {
550-
// Empty validator handshake message
551-
return nil
552-
}
553-
673+
func (m *Message) DecodeMessage() error {
674+
var err error
554675
switch m.Code {
555676
case MsgPreprepare:
556677
var p *Preprepare
@@ -598,7 +719,23 @@ func (m *Message) DecodeRLP(stream *rlp.Stream) error {
598719
err = fmt.Errorf("unrecognised message code %d", m.Code)
599720
}
600721
return err
722+
}
723+
724+
func (m *Message) DecodeRLP(stream *rlp.Stream) error {
725+
type decodable Message
726+
var d decodable
727+
err := stream.Decode(&d)
728+
if err != nil {
729+
return err
730+
}
731+
*m = Message(d)
732+
733+
if len(m.Msg) == 0 && len(m.Signature) == 0 {
734+
// Empty validator handshake message
735+
return nil
736+
}
601737

738+
return m.DecodeMessage()
602739
}
603740

604741
// FromPayload decodes b into a Message instance it will set one of the private
@@ -666,6 +803,20 @@ func (m *Message) Prepare() *Subject {
666803
return m.prepare
667804
}
668805

806+
// Prepare returns round change if this is a round change message.
807+
func (m *Message) TryRoundChange() (*RoundChange, error) {
808+
if m.roundChange != nil {
809+
return m.roundChange, nil
810+
}
811+
if m.Code != MsgRoundChange {
812+
return nil, fmt.Errorf("expected round change message, received code: %d", m.Code)
813+
}
814+
if err := m.DecodeMessage(); err != nil {
815+
return nil, err
816+
}
817+
return m.roundChange, nil
818+
}
819+
669820
// Prepare returns round change if this is a round change message.
670821
func (m *Message) RoundChange() *RoundChange {
671822
return m.roundChange

Diff for: consensus/istanbul/types_test.go

+60-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/celo-org/celo-blockchain/core/types"
2727
"github.com/celo-org/celo-blockchain/rlp"
2828
"golang.org/x/crypto/sha3"
29+
"gotest.tools/assert"
2930
)
3031

3132
// testHasher is the helper tool for transaction/receipt list hashing.
@@ -134,9 +135,29 @@ func dummyMessage(code uint64) *Message {
134135
return msg
135136
}
136137

138+
func dummyRoundChangeMessage() *Message {
139+
msg := NewPrepareMessage(dummySubject(), common.HexToAddress("AABB"))
140+
// Set empty rather than nil signature since this is how rlp decodes non
141+
// existent slices.
142+
msg.Signature = []byte{}
143+
msg.Code = MsgRoundChange
144+
roundChange := &RoundChange{
145+
View: &View{
146+
Round: common.Big1,
147+
Sequence: common.Big2,
148+
},
149+
PreparedCertificate: PreparedCertificate{
150+
PrepareOrCommitMessages: []Message{},
151+
Proposal: dummyBlock(2),
152+
},
153+
}
154+
setMessageBytes(msg, roundChange)
155+
return msg
156+
}
157+
137158
func dummyRoundChangeCertificate() *RoundChangeCertificate {
138159
return &RoundChangeCertificate{
139-
RoundChangeMessages: []Message{*dummyMessage(42), *dummyMessage(32), *dummyMessage(15)},
160+
RoundChangeMessages: []Message{*dummyRoundChangeMessage(), *dummyRoundChangeMessage(), *dummyRoundChangeMessage()},
140161
}
141162
}
142163

@@ -202,6 +223,35 @@ func TestRoundChangeCertificateRLPEncoding(t *testing.T) {
202223
t.Fatalf("Error %v", err)
203224
}
204225

226+
assert.Equal(t, len(original.RoundChangeMessages), len(original.RoundChangeMessages))
227+
o1 := original.RoundChangeMessages[0]
228+
r1 := result.RoundChangeMessages[0]
229+
if !reflect.DeepEqual(o1.Code, r1.Code) {
230+
t.Fatalf("RLP Encode/Decode mismatch at first Code")
231+
}
232+
233+
if !reflect.DeepEqual(o1.Code, r1.Code) {
234+
t.Fatalf("RLP Encode/Decode mismatch at first Code")
235+
}
236+
237+
if !reflect.DeepEqual(o1.Address, r1.Address) {
238+
t.Fatalf("RLP Encode/Decode mismatch at first Address")
239+
}
240+
241+
if !reflect.DeepEqual(o1.Signature, r1.Signature) {
242+
t.Fatalf("RLP Encode/Decode mismatch at first Signature")
243+
}
244+
245+
if !reflect.DeepEqual(o1.Msg, r1.Msg) {
246+
t.Fatalf("RLP Encode/Decode mismatch at first internal Msg bytes. %v ----- %v", o1.Msg, r1.Msg)
247+
}
248+
249+
original.RoundChangeMessages[0].prepare = nil
250+
original.RoundChangeMessages[1].prepare = nil
251+
original.RoundChangeMessages[2].prepare = nil
252+
result.RoundChangeMessages[0].roundChange = nil
253+
result.RoundChangeMessages[1].roundChange = nil
254+
result.RoundChangeMessages[2].roundChange = nil
205255
if !reflect.DeepEqual(original, result) {
206256
t.Fatalf("RLP Encode/Decode mismatch. Got %v, expected %v", result, original)
207257
}
@@ -224,6 +274,15 @@ func TestPreprepareRLPEncoding(t *testing.T) {
224274
t.Fatalf("Error %v", err)
225275
}
226276

277+
o := original.RoundChangeCertificate
278+
o.RoundChangeMessages[0].prepare = nil
279+
o.RoundChangeMessages[1].prepare = nil
280+
o.RoundChangeMessages[2].prepare = nil
281+
r := result.RoundChangeCertificate
282+
r.RoundChangeMessages[0].roundChange = nil
283+
r.RoundChangeMessages[1].roundChange = nil
284+
r.RoundChangeMessages[2].roundChange = nil
285+
227286
// decoded Blocks don't equal Original ones so we need to check equality differently
228287
assertEqual(t, "RLP Encode/Decode mismatch: View", result.View, original.View)
229288
assertEqual(t, "RLP Encode/Decode mismatch: RoundChangeCertificate", result.RoundChangeCertificate, original.RoundChangeCertificate)

Diff for: eth/protocols/eth/handler.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo {
162162
func Handle(backend Backend, peer *Peer) error {
163163
for {
164164
if err := handleMessage(backend, peer); err != nil {
165-
peer.Log().Warn("Message handling failed in `eth`", "err", err)
165+
peer.Log().Debug("Message handling failed in `eth`", "err", err)
166166
return err
167167
}
168168
}

Diff for: eth/protocols/eth/protocol.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const ProtocolName = "eth"
4343
var ProtocolVersions = []uint{ETH66, ETH65}
4444

4545
// maxMessageSize is the maximum cap on the size of a protocol message.
46-
const maxMessageSize = 10 * 1024 * 1024 * 10 // Celo increase for istanbul round change certificates
46+
const maxMessageSize = 10 * 1024 * 1024
4747

4848
const (
4949
// Protocol messages in eth/64 (celo65)

Diff for: params/version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
const (
2828
VersionMajor = 1 // Major version component of the current release
2929
VersionMinor = 5 // Minor version component of the current release
30-
VersionPatch = 7 // Patch version component of the current release
30+
VersionPatch = 8 // Patch version component of the current release
3131
VersionMeta = "stable" // Version metadata to append to the version string
3232
)
3333

0 commit comments

Comments
 (0)