Skip to content

Commit b77e8c3

Browse files
Separate QuorumReceiptExtraData from receipt encoding (#1227)
* separate PSReceipts and RevertReason fields to QuorumReceiptExtraData structure * separate PSReceipts and RevertReason fields to QuorumReceiptExtraData structure * reduce receipt code changes impact when compared with pre-mps receipts code (should make it easier to merge upstream changes) * Receipt extends QuorumReceiptExtraData * add unit tests for QuorumReceiptExtraData rlp encoding * add unit tests for QuorumReceiptExtraData rlp encoding (when reading older receipt encodings) * add procedure for introducing a new field to the QuorumReceiptExtraData structure, alter the WriteReceipts logic to only append the quorumReceiptsExtraData if it is not empty * add procedure for introducing a new field to the QuorumReceiptExtraData structure, alter the WriteReceipts logic to only append the quorumReceiptsExtraData if it is not empty * fix typo
1 parent 97395bd commit b77e8c3

File tree

5 files changed

+536
-426
lines changed

5 files changed

+536
-426
lines changed

core/rawdb/accessors_chain.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,16 +599,36 @@ func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Rec
599599
if len(data) == 0 {
600600
return nil
601601
}
602+
_, extraData, err := rlp.SplitList(data)
603+
if err != nil {
604+
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
605+
return nil
606+
}
607+
// vanillaData does not include the header bytes so go back and get the slice from pos 0
608+
vanillaDataWithListHeader := data[0 : len(data)-len(extraData)]
609+
602610
// Convert the receipts from their storage form to their internal representation
603611
storageReceipts := []*types.ReceiptForStorage{}
604-
if err := rlp.DecodeBytes(data, &storageReceipts); err != nil {
612+
if err := rlp.DecodeBytes(vanillaDataWithListHeader, &storageReceipts); err != nil {
605613
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
606614
return nil
607615
}
608616
receipts := make(types.Receipts, len(storageReceipts))
609617
for i, storageReceipt := range storageReceipts {
610618
receipts[i] = (*types.Receipt)(storageReceipt)
611619
}
620+
if len(extraData) > 0 {
621+
quorumExtraDataReceipts := []*types.QuorumReceiptExtraData{}
622+
if err := rlp.DecodeBytes(extraData, &quorumExtraDataReceipts); err != nil {
623+
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
624+
return nil
625+
}
626+
for i, quorumExtraDataReceipt := range quorumExtraDataReceipts {
627+
if quorumExtraDataReceipt != nil {
628+
receipts[i].FillReceiptExtraDataFromStorage(quorumExtraDataReceipt)
629+
}
630+
}
631+
}
612632
return receipts
613633
}
614634

@@ -641,13 +661,27 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para
641661
func WriteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, receipts types.Receipts) {
642662
// Convert the receipts into their storage form and serialize them
643663
storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
664+
quorumReceiptsExtraData := make([]*types.QuorumReceiptExtraData, len(receipts))
665+
extraDataEmpty := true
644666
for i, receipt := range receipts {
645667
storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
668+
quorumReceiptsExtraData[i] = &receipt.QuorumReceiptExtraData
669+
if !receipt.QuorumReceiptExtraData.IsEmpty() {
670+
extraDataEmpty = false
671+
}
646672
}
647673
bytes, err := rlp.EncodeToBytes(storageReceipts)
648674
if err != nil {
649675
log.Crit("Failed to encode block receipts", "err", err)
650676
}
677+
if !extraDataEmpty {
678+
bytesExtraData, err := rlp.EncodeToBytes(quorumReceiptsExtraData)
679+
if err != nil {
680+
log.Crit("Failed to encode block receipts", "err", err)
681+
}
682+
// the vanilla receipts and the extra data receipts are concatenated and stored as a single value
683+
bytes = append(bytes, bytesExtraData...)
684+
}
651685
// Store the flattened receipt slice
652686
if err := db.Put(blockReceiptsKey(number, hash), bytes); err != nil {
653687
log.Crit("Failed to store block receipts", "err", err)

core/rawdb/accessors_chain_test.go

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ import (
2828

2929
"github.com/ethereum/go-ethereum/common"
3030
"github.com/ethereum/go-ethereum/core/types"
31+
"github.com/ethereum/go-ethereum/ethdb"
32+
"github.com/ethereum/go-ethereum/log"
3133
"github.com/ethereum/go-ethereum/params"
3234
"github.com/ethereum/go-ethereum/rlp"
35+
"github.com/stretchr/testify/assert"
3336
"golang.org/x/crypto/sha3"
3437
)
3538

@@ -323,6 +326,262 @@ func TestBlockReceiptStorage(t *testing.T) {
323326
if err := checkReceiptsRLP(rs, receipts); err != nil {
324327
t.Fatalf(err.Error())
325328
}
329+
// check that the raw data does not contain the quorumExtraData array (since the prepared receipts do not have any quorumExtraData)
330+
receiptData := ReadReceiptsRLP(db, hash, 0)
331+
_, extraData, err := rlp.SplitList(receiptData)
332+
assert.NoError(t, err)
333+
assert.Empty(t, extraData)
334+
}
335+
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
336+
DeleteBody(db, hash, 0)
337+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); rs != nil {
338+
t.Fatalf("receipts returned when body was deleted: %v", rs)
339+
}
340+
// Ensure that receipts without metadata can be returned without the block body too
341+
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
342+
t.Fatalf(err.Error())
343+
}
344+
// Sanity check that body alone without the receipt is a full purge
345+
WriteBody(db, hash, 0, body)
346+
347+
DeleteReceipts(db, hash, 0)
348+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
349+
t.Fatalf("deleted receipts returned: %v", rs)
350+
}
351+
}
352+
353+
func TestBlockReceiptStorageWithLegacyMPSV1EncodingWithMPSData(t *testing.T) {
354+
db := NewMemoryDatabase()
355+
356+
// Create a live block since we need metadata to reconstruct the receipt
357+
tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
358+
tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)
359+
tx2.SetPrivate()
360+
361+
body := &types.Body{Transactions: types.Transactions{tx1, tx2}}
362+
363+
// Create the two receipts to manage afterwards
364+
receipt1 := &types.Receipt{
365+
Status: types.ReceiptStatusFailed,
366+
CumulativeGasUsed: 1,
367+
Logs: []*types.Log{
368+
{Address: common.BytesToAddress([]byte{0x11})},
369+
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
370+
},
371+
TxHash: tx1.Hash(),
372+
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
373+
GasUsed: 111111,
374+
}
375+
receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})
376+
377+
psiReceipt2 := &types.Receipt{
378+
PostState: common.Hash{2}.Bytes(),
379+
CumulativeGasUsed: 2,
380+
Logs: []*types.Log{
381+
{Address: common.BytesToAddress([]byte{0x22})},
382+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
383+
},
384+
TxHash: tx2.Hash(),
385+
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
386+
GasUsed: 222222,
387+
}
388+
389+
receipt2 := &types.Receipt{
390+
PostState: common.Hash{2}.Bytes(),
391+
CumulativeGasUsed: 2,
392+
Logs: []*types.Log{
393+
{Address: common.BytesToAddress([]byte{0x22})},
394+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
395+
},
396+
TxHash: tx2.Hash(),
397+
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
398+
GasUsed: 222222,
399+
QuorumReceiptExtraData: types.QuorumReceiptExtraData{
400+
PSReceipts: map[types.PrivateStateIdentifier]*types.Receipt{types.PrivateStateIdentifier("psi1"): psiReceipt2},
401+
},
402+
}
403+
receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2})
404+
receipts := []*types.Receipt{receipt1, receipt2}
405+
406+
// Check that no receipt entries are in a pristine database
407+
hash := common.BytesToHash([]byte{0x03, 0x14})
408+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
409+
t.Fatalf("non existent receipts returned: %v", rs)
410+
}
411+
// Insert the body that corresponds to the receipts
412+
WriteBody(db, hash, 0, body)
413+
414+
// Insert the receipt slice into the database and check presence
415+
WriteReceiptsMPSV1(db, hash, 0, receipts)
416+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
417+
t.Fatalf("no receipts returned")
418+
} else {
419+
if err := checkReceiptsRLP(rs, receipts); err != nil {
420+
t.Fatalf(err.Error())
421+
}
422+
rec2 := rs[1]
423+
assert.Len(t, rec2.PSReceipts, 1)
424+
psRec2 := rec2.PSReceipts[types.PrivateStateIdentifier("psi1")]
425+
assert.NotNil(t, psRec2)
426+
}
427+
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
428+
DeleteBody(db, hash, 0)
429+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); rs != nil {
430+
t.Fatalf("receipts returned when body was deleted: %v", rs)
431+
}
432+
// Ensure that receipts without metadata can be returned without the block body too
433+
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
434+
t.Fatalf(err.Error())
435+
}
436+
}
437+
438+
func TestBlockReceiptStorageWithLegacyMPSV1EncodingWithoutMPSData(t *testing.T) {
439+
db := NewMemoryDatabase()
440+
441+
// Create a live block since we need metadata to reconstruct the receipt
442+
tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
443+
tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)
444+
tx2.SetPrivate()
445+
446+
body := &types.Body{Transactions: types.Transactions{tx1, tx2}}
447+
448+
// Create the two receipts to manage afterwards
449+
receipt1 := &types.Receipt{
450+
Status: types.ReceiptStatusFailed,
451+
CumulativeGasUsed: 1,
452+
Logs: []*types.Log{
453+
{Address: common.BytesToAddress([]byte{0x11})},
454+
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
455+
},
456+
TxHash: tx1.Hash(),
457+
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
458+
GasUsed: 111111,
459+
}
460+
receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})
461+
462+
receipt2 := &types.Receipt{
463+
PostState: common.Hash{2}.Bytes(),
464+
CumulativeGasUsed: 2,
465+
Logs: []*types.Log{
466+
{Address: common.BytesToAddress([]byte{0x22})},
467+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
468+
},
469+
TxHash: tx2.Hash(),
470+
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
471+
GasUsed: 222222,
472+
}
473+
receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2})
474+
receipts := []*types.Receipt{receipt1, receipt2}
475+
476+
// Check that no receipt entries are in a pristine database
477+
hash := common.BytesToHash([]byte{0x03, 0x14})
478+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
479+
t.Fatalf("non existent receipts returned: %v", rs)
480+
}
481+
// Insert the body that corresponds to the receipts
482+
WriteBody(db, hash, 0, body)
483+
484+
// Insert the receipt slice into the database and check presence
485+
WriteReceiptsMPSV1(db, hash, 0, receipts)
486+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
487+
t.Fatalf("no receipts returned")
488+
} else {
489+
if err := checkReceiptsRLP(rs, receipts); err != nil {
490+
t.Fatalf(err.Error())
491+
}
492+
rec2 := rs[1]
493+
assert.Len(t, rec2.PSReceipts, 0)
494+
}
495+
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
496+
DeleteBody(db, hash, 0)
497+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); rs != nil {
498+
t.Fatalf("receipts returned when body was deleted: %v", rs)
499+
}
500+
// Ensure that receipts without metadata can be returned without the block body too
501+
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
502+
t.Fatalf(err.Error())
503+
}
504+
}
505+
506+
// Tests that receipts associated with a single block can be stored and retrieved.
507+
func TestBlockReceiptStorageWithQuorumExtraData(t *testing.T) {
508+
db := NewMemoryDatabase()
509+
510+
// Create a live block since we need metadata to reconstruct the receipt
511+
tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
512+
tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)
513+
tx2.SetPrivate()
514+
515+
body := &types.Body{Transactions: types.Transactions{tx1, tx2}}
516+
517+
// Create the two receipts to manage afterwards
518+
receipt1 := &types.Receipt{
519+
Status: types.ReceiptStatusFailed,
520+
CumulativeGasUsed: 1,
521+
Logs: []*types.Log{
522+
{Address: common.BytesToAddress([]byte{0x11})},
523+
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
524+
},
525+
TxHash: tx1.Hash(),
526+
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
527+
GasUsed: 111111,
528+
}
529+
receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})
530+
531+
psiReceipt2 := &types.Receipt{
532+
PostState: common.Hash{2}.Bytes(),
533+
CumulativeGasUsed: 2,
534+
Logs: []*types.Log{
535+
{Address: common.BytesToAddress([]byte{0x22})},
536+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
537+
},
538+
TxHash: tx2.Hash(),
539+
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
540+
GasUsed: 222222,
541+
QuorumReceiptExtraData: types.QuorumReceiptExtraData{
542+
RevertReason: []byte("arbitraryvalue"),
543+
},
544+
}
545+
546+
receipt2 := &types.Receipt{
547+
PostState: common.Hash{2}.Bytes(),
548+
CumulativeGasUsed: 2,
549+
Logs: []*types.Log{
550+
{Address: common.BytesToAddress([]byte{0x22})},
551+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
552+
},
553+
TxHash: tx2.Hash(),
554+
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
555+
GasUsed: 222222,
556+
QuorumReceiptExtraData: types.QuorumReceiptExtraData{
557+
RevertReason: []byte("arbitraryvalue"),
558+
PSReceipts: map[types.PrivateStateIdentifier]*types.Receipt{types.PrivateStateIdentifier("psi1"): psiReceipt2},
559+
},
560+
}
561+
receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2})
562+
receipts := []*types.Receipt{receipt1, receipt2}
563+
564+
// Check that no receipt entries are in a pristine database
565+
hash := common.BytesToHash([]byte{0x03, 0x14})
566+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
567+
t.Fatalf("non existent receipts returned: %v", rs)
568+
}
569+
// Insert the body that corresponds to the receipts
570+
WriteBody(db, hash, 0, body)
571+
572+
// Insert the receipt slice into the database and check presence
573+
WriteReceipts(db, hash, 0, receipts)
574+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
575+
t.Fatalf("no receipts returned")
576+
} else {
577+
if err := checkReceiptsRLP(rs, receipts); err != nil {
578+
t.Fatalf(err.Error())
579+
}
580+
rec2 := rs[1]
581+
assert.Len(t, rec2.PSReceipts, 1)
582+
assert.Equal(t, rec2.RevertReason, []byte("arbitraryvalue"))
583+
psRec2 := rec2.PSReceipts[types.PrivateStateIdentifier("psi1")]
584+
assert.Equal(t, psRec2.RevertReason, []byte("arbitraryvalue"))
326585
}
327586
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
328587
DeleteBody(db, hash, 0)
@@ -457,3 +716,19 @@ func TestCanonicalHashIteration(t *testing.T) {
457716
}
458717
}
459718
}
719+
720+
func WriteReceiptsMPSV1(db ethdb.KeyValueWriter, hash common.Hash, number uint64, receipts types.Receipts) {
721+
// Convert the receipts into their storage form and serialize them
722+
storageReceipts := make([]*types.ReceiptForStorageMPSV1, len(receipts))
723+
for i, receipt := range receipts {
724+
storageReceipts[i] = (*types.ReceiptForStorageMPSV1)(receipt)
725+
}
726+
bytes, err := rlp.EncodeToBytes(storageReceipts)
727+
if err != nil {
728+
log.Crit("Failed to encode block receipts", "err", err)
729+
}
730+
// Store the flattened receipt slice
731+
if err := db.Put(blockReceiptsKey(number, hash), bytes); err != nil {
732+
log.Crit("Failed to store block receipts", "err", err)
733+
}
734+
}

core/state_processor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,10 @@ func ApplyTransactionOnMPS(config *params.ChainConfig, bc *BlockChain, author *c
171171
// clone the gas pool (as we don't want to keep consuming intrinsic gas multiple times for each MPS execution)
172172
gp := new(GasPool).AddGas(originalGP.Gas())
173173
mpsReceipt := &types.Receipt{
174-
PSReceipts: make(map[types.PrivateStateIdentifier]*types.Receipt),
175-
Logs: make([]*types.Log, 0),
174+
QuorumReceiptExtraData: types.QuorumReceiptExtraData{
175+
PSReceipts: make(map[types.PrivateStateIdentifier]*types.Receipt),
176+
},
177+
Logs: make([]*types.Log, 0),
176178
}
177179
_, managedParties, _, _, err := private.P.Receive(common.BytesToEncryptedPayloadHash(tx.Data()))
178180
if err != nil {

0 commit comments

Comments
 (0)