diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go
index 3ffa9c2feb1a6ce3790c46dd1fd72d500fe56ca6..90e1c14d9017d4bd50b2f847576b0b238df6cb42 100644
--- a/core/types/gen_log_json.go
+++ b/core/types/gen_log_json.go
@@ -18,12 +18,12 @@ func (l Log) MarshalJSON() ([]byte, error) {
 		Address     common.Address `json:"address" gencodec:"required"`
 		Topics      []common.Hash  `json:"topics" gencodec:"required"`
 		Data        hexutil.Bytes  `json:"data" gencodec:"required"`
-		BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"`
-		TxHash      common.Hash    `json:"transactionHash" gencodec:"required" rlp:"-"`
-		TxIndex     hexutil.Uint   `json:"transactionIndex" rlp:"-"`
-		BlockHash   common.Hash    `json:"blockHash" rlp:"-"`
-		Index       hexutil.Uint   `json:"logIndex" rlp:"-"`
-		Removed     bool           `json:"removed" rlp:"-"`
+		BlockNumber hexutil.Uint64 `json:"blockNumber"`
+		TxHash      common.Hash    `json:"transactionHash" gencodec:"required"`
+		TxIndex     hexutil.Uint   `json:"transactionIndex"`
+		BlockHash   common.Hash    `json:"blockHash"`
+		Index       hexutil.Uint   `json:"logIndex"`
+		Removed     bool           `json:"removed"`
 	}
 	var enc Log
 	enc.Address = l.Address
@@ -44,12 +44,12 @@ func (l *Log) UnmarshalJSON(input []byte) error {
 		Address     *common.Address `json:"address" gencodec:"required"`
 		Topics      []common.Hash   `json:"topics" gencodec:"required"`
 		Data        *hexutil.Bytes  `json:"data" gencodec:"required"`
-		BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"`
-		TxHash      *common.Hash    `json:"transactionHash" gencodec:"required" rlp:"-"`
-		TxIndex     *hexutil.Uint   `json:"transactionIndex" rlp:"-"`
-		BlockHash   *common.Hash    `json:"blockHash" rlp:"-"`
-		Index       *hexutil.Uint   `json:"logIndex" rlp:"-"`
-		Removed     *bool           `json:"removed" rlp:"-"`
+		BlockNumber *hexutil.Uint64 `json:"blockNumber"`
+		TxHash      *common.Hash    `json:"transactionHash" gencodec:"required"`
+		TxIndex     *hexutil.Uint   `json:"transactionIndex"`
+		BlockHash   *common.Hash    `json:"blockHash"`
+		Index       *hexutil.Uint   `json:"logIndex"`
+		Removed     *bool           `json:"removed"`
 	}
 	var dec Log
 	if err := json.Unmarshal(input, &dec); err != nil {
diff --git a/core/types/log.go b/core/types/log.go
index 067708db364b492c5d83141b711a8567a1aad889..88274e39dae0bdae04d820f5074d3e81f8b4fed5 100644
--- a/core/types/log.go
+++ b/core/types/log.go
@@ -17,8 +17,11 @@
 package types
 
 import (
+	"io"
+
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/rlp"
 )
 
 //go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go
@@ -37,19 +40,19 @@ type Log struct {
 	// Derived fields. These fields are filled in by the node
 	// but not secured by consensus.
 	// block in which the transaction was included
-	BlockNumber uint64 `json:"blockNumber" rlp:"-"`
+	BlockNumber uint64 `json:"blockNumber"`
 	// hash of the transaction
-	TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
+	TxHash common.Hash `json:"transactionHash" gencodec:"required"`
 	// index of the transaction in the block
-	TxIndex uint `json:"transactionIndex" rlp:"-"`
+	TxIndex uint `json:"transactionIndex"`
 	// hash of the block in which the transaction was included
-	BlockHash common.Hash `json:"blockHash" rlp:"-"`
+	BlockHash common.Hash `json:"blockHash"`
 	// index of the log in the block
-	Index uint `json:"logIndex" rlp:"-"`
+	Index uint `json:"logIndex"`
 
 	// The Removed field is true if this log was reverted due to a chain reorganisation.
 	// You must pay attention to this field if you receive logs through a filter query.
-	Removed bool `json:"removed" rlp:"-"`
+	Removed bool `json:"removed"`
 }
 
 type logMarshaling struct {
@@ -58,3 +61,83 @@ type logMarshaling struct {
 	TxIndex     hexutil.Uint
 	Index       hexutil.Uint
 }
+
+type rlpLog struct {
+	Address common.Address
+	Topics  []common.Hash
+	Data    []byte
+}
+
+// rlpStorageLog is the storage encoding of a log.
+type rlpStorageLog rlpLog
+
+// legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields.
+type legacyRlpStorageLog struct {
+	Address     common.Address
+	Topics      []common.Hash
+	Data        []byte
+	BlockNumber uint64
+	TxHash      common.Hash
+	TxIndex     uint
+	BlockHash   common.Hash
+	Index       uint
+}
+
+// EncodeRLP implements rlp.Encoder.
+func (l *Log) EncodeRLP(w io.Writer) error {
+	return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data})
+}
+
+// DecodeRLP implements rlp.Decoder.
+func (l *Log) DecodeRLP(s *rlp.Stream) error {
+	var dec rlpLog
+	err := s.Decode(&dec)
+	if err == nil {
+		l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data
+	}
+	return err
+}
+
+// LogForStorage is a wrapper around a Log that flattens and parses the entire content of
+// a log including non-consensus fields.
+type LogForStorage Log
+
+// EncodeRLP implements rlp.Encoder.
+func (l *LogForStorage) EncodeRLP(w io.Writer) error {
+	return rlp.Encode(w, rlpStorageLog{
+		Address: l.Address,
+		Topics:  l.Topics,
+		Data:    l.Data,
+	})
+}
+
+// DecodeRLP implements rlp.Decoder.
+//
+// Note some redundant fields(e.g. block number, tx hash etc) will be assembled later.
+func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error {
+	blob, err := s.Raw()
+	if err != nil {
+		return err
+	}
+	var dec rlpStorageLog
+	err = rlp.DecodeBytes(blob, &dec)
+	if err == nil {
+		*l = LogForStorage{
+			Address: dec.Address,
+			Topics:  dec.Topics,
+			Data:    dec.Data,
+		}
+	} else {
+		// Try to decode log with previous definition.
+		var dec legacyRlpStorageLog
+		err = rlp.DecodeBytes(blob, &dec)
+		if err == nil {
+			*l = LogForStorage{
+				Address: dec.Address,
+				Topics:  dec.Topics,
+				Data:    dec.Data,
+			}
+		}
+	}
+	return err
+}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index d9029ca2e522a719592c60fc4382cb46a89bb914..6141740f2f132cf8a0b43038a9719d1a26e73af9 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -94,7 +94,28 @@ type receiptRLP struct {
 type storedReceiptRLP struct {
 	PostStateOrStatus []byte
 	CumulativeGasUsed uint64
-	Logs              []*Log
+	Logs              []*LogForStorage
+}
+
+// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4.
+type v4StoredReceiptRLP struct {
+	PostStateOrStatus []byte
+	CumulativeGasUsed uint64
+	TxHash            common.Hash
+	ContractAddress   common.Address
+	Logs              []*LogForStorage
+	GasUsed           uint64
+}
+
+// v3StoredReceiptRLP is the original storage encoding of a receipt including some unnecessary fields.
+type v3StoredReceiptRLP struct {
+	PostStateOrStatus []byte
+	CumulativeGasUsed uint64
+	Bloom             Bloom
+	TxHash            common.Hash
+	ContractAddress   common.Address
+	Logs              []*LogForStorage
+	GasUsed           uint64
 }
 
 // NewReceipt creates a barebone transaction receipt, copying the init fields.
@@ -212,28 +233,96 @@ func (r *Receipt) Size() common.StorageSize {
 // entire content of a receipt, as opposed to only the consensus fields originally.
 type ReceiptForStorage Receipt
 
-// EncodeRLP implements rlp.Encoder.
+// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
+// into an RLP stream.
 func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
 	enc := &storedReceiptRLP{
 		PostStateOrStatus: (*Receipt)(r).statusEncoding(),
 		CumulativeGasUsed: r.CumulativeGasUsed,
-		Logs:              r.Logs,
+		Logs:              make([]*LogForStorage, len(r.Logs)),
+	}
+	for i, log := range r.Logs {
+		enc.Logs[i] = (*LogForStorage)(log)
 	}
 	return rlp.Encode(w, enc)
 }
 
-// DecodeRLP implements rlp.Decoder.
+// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation
+// fields of a receipt from an RLP stream.
 func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
+	// Retrieve the entire receipt blob as we need to try multiple decoders
+	blob, err := s.Raw()
+	if err != nil {
+		return err
+	}
+	// Try decoding from the newest format for future proofness, then the older one
+	// for old nodes that just upgraded. V4 was an intermediate unreleased format so
+	// we do need to decode it, but it's not common (try last).
+	if err := decodeStoredReceiptRLP(r, blob); err == nil {
+		return nil
+	}
+	if err := decodeV3StoredReceiptRLP(r, blob); err == nil {
+		return nil
+	}
+	return decodeV4StoredReceiptRLP(r, blob)
+}
+
+func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
 	var stored storedReceiptRLP
-	if err := s.Decode(&stored); err != nil {
+	if err := rlp.DecodeBytes(blob, &stored); err != nil {
 		return err
 	}
 	if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
 		return err
 	}
 	r.CumulativeGasUsed = stored.CumulativeGasUsed
-	r.Logs = stored.Logs
+	r.Logs = make([]*Log, len(stored.Logs))
+	for i, log := range stored.Logs {
+		r.Logs[i] = (*Log)(log)
+	}
 	r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
+
+	return nil
+}
+
+func decodeV4StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
+	var stored v4StoredReceiptRLP
+	if err := rlp.DecodeBytes(blob, &stored); err != nil {
+		return err
+	}
+	if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
+		return err
+	}
+	r.CumulativeGasUsed = stored.CumulativeGasUsed
+	r.TxHash = stored.TxHash
+	r.ContractAddress = stored.ContractAddress
+	r.GasUsed = stored.GasUsed
+	r.Logs = make([]*Log, len(stored.Logs))
+	for i, log := range stored.Logs {
+		r.Logs[i] = (*Log)(log)
+	}
+	r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
+
+	return nil
+}
+
+func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
+	var stored v3StoredReceiptRLP
+	if err := rlp.DecodeBytes(blob, &stored); err != nil {
+		return err
+	}
+	if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
+		return err
+	}
+	r.CumulativeGasUsed = stored.CumulativeGasUsed
+	r.Bloom = stored.Bloom
+	r.TxHash = stored.TxHash
+	r.ContractAddress = stored.ContractAddress
+	r.GasUsed = stored.GasUsed
+	r.Logs = make([]*Log, len(stored.Logs))
+	for i, log := range stored.Logs {
+		r.Logs[i] = (*Log)(log)
+	}
 	return nil
 }
 
diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go
index 87fc16a5105b6a7eb7b318c81dfb4ebd956af0eb..22a316c2374b7382bfff4d4f5c73da6ea98fb077 100644
--- a/core/types/receipt_test.go
+++ b/core/types/receipt_test.go
@@ -20,6 +20,7 @@ import (
 	"bytes"
 	"math"
 	"math/big"
+	"reflect"
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -37,6 +38,128 @@ func TestDecodeEmptyTypedReceipt(t *testing.T) {
 	}
 }
 
+func TestLegacyReceiptDecoding(t *testing.T) {
+	tests := []struct {
+		name   string
+		encode func(*Receipt) ([]byte, error)
+	}{
+		{
+			"StoredReceiptRLP",
+			encodeAsStoredReceiptRLP,
+		},
+		{
+			"V4StoredReceiptRLP",
+			encodeAsV4StoredReceiptRLP,
+		},
+		{
+			"V3StoredReceiptRLP",
+			encodeAsV3StoredReceiptRLP,
+		},
+	}
+
+	tx := NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
+	receipt := &Receipt{
+		Status:            ReceiptStatusFailed,
+		CumulativeGasUsed: 1,
+		Logs: []*Log{
+			{
+				Address: common.BytesToAddress([]byte{0x11}),
+				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
+				Data:    []byte{0x01, 0x00, 0xff},
+			},
+			{
+				Address: common.BytesToAddress([]byte{0x01, 0x11}),
+				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
+				Data:    []byte{0x01, 0x00, 0xff},
+			},
+		},
+		TxHash:          tx.Hash(),
+		ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
+		GasUsed:         111111,
+	}
+	receipt.Bloom = CreateBloom(Receipts{receipt})
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			enc, err := tc.encode(receipt)
+			if err != nil {
+				t.Fatalf("Error encoding receipt: %v", err)
+			}
+			var dec ReceiptForStorage
+			if err := rlp.DecodeBytes(enc, &dec); err != nil {
+				t.Fatalf("Error decoding RLP receipt: %v", err)
+			}
+			// Check whether all consensus fields are correct.
+			if dec.Status != receipt.Status {
+				t.Fatalf("Receipt status mismatch, want %v, have %v", receipt.Status, dec.Status)
+			}
+			if dec.CumulativeGasUsed != receipt.CumulativeGasUsed {
+				t.Fatalf("Receipt CumulativeGasUsed mismatch, want %v, have %v", receipt.CumulativeGasUsed, dec.CumulativeGasUsed)
+			}
+			if dec.Bloom != receipt.Bloom {
+				t.Fatalf("Bloom data mismatch, want %v, have %v", receipt.Bloom, dec.Bloom)
+			}
+			if len(dec.Logs) != len(receipt.Logs) {
+				t.Fatalf("Receipt log number mismatch, want %v, have %v", len(receipt.Logs), len(dec.Logs))
+			}
+			for i := 0; i < len(dec.Logs); i++ {
+				if dec.Logs[i].Address != receipt.Logs[i].Address {
+					t.Fatalf("Receipt log %d address mismatch, want %v, have %v", i, receipt.Logs[i].Address, dec.Logs[i].Address)
+				}
+				if !reflect.DeepEqual(dec.Logs[i].Topics, receipt.Logs[i].Topics) {
+					t.Fatalf("Receipt log %d topics mismatch, want %v, have %v", i, receipt.Logs[i].Topics, dec.Logs[i].Topics)
+				}
+				if !bytes.Equal(dec.Logs[i].Data, receipt.Logs[i].Data) {
+					t.Fatalf("Receipt log %d data mismatch, want %v, have %v", i, receipt.Logs[i].Data, dec.Logs[i].Data)
+				}
+			}
+		})
+	}
+}
+
+func encodeAsStoredReceiptRLP(want *Receipt) ([]byte, error) {
+	stored := &storedReceiptRLP{
+		PostStateOrStatus: want.statusEncoding(),
+		CumulativeGasUsed: want.CumulativeGasUsed,
+		Logs:              make([]*LogForStorage, len(want.Logs)),
+	}
+	for i, log := range want.Logs {
+		stored.Logs[i] = (*LogForStorage)(log)
+	}
+	return rlp.EncodeToBytes(stored)
+}
+
+func encodeAsV4StoredReceiptRLP(want *Receipt) ([]byte, error) {
+	stored := &v4StoredReceiptRLP{
+		PostStateOrStatus: want.statusEncoding(),
+		CumulativeGasUsed: want.CumulativeGasUsed,
+		TxHash:            want.TxHash,
+		ContractAddress:   want.ContractAddress,
+		Logs:              make([]*LogForStorage, len(want.Logs)),
+		GasUsed:           want.GasUsed,
+	}
+	for i, log := range want.Logs {
+		stored.Logs[i] = (*LogForStorage)(log)
+	}
+	return rlp.EncodeToBytes(stored)
+}
+
+func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
+	stored := &v3StoredReceiptRLP{
+		PostStateOrStatus: want.statusEncoding(),
+		CumulativeGasUsed: want.CumulativeGasUsed,
+		Bloom:             want.Bloom,
+		TxHash:            want.TxHash,
+		ContractAddress:   want.ContractAddress,
+		Logs:              make([]*LogForStorage, len(want.Logs)),
+		GasUsed:           want.GasUsed,
+	}
+	for i, log := range want.Logs {
+		stored.Logs[i] = (*LogForStorage)(log)
+	}
+	return rlp.EncodeToBytes(stored)
+}
+
 // Tests that receipt data can be correctly derived from the contextual infos
 func TestDeriveFields(t *testing.T) {
 	// Create a few transactions to have receipts for