Skip to content

Commit d2e159e

Browse files
committed
etcm-70-71 added checkpoint to block header + updated header decoder/encoder + added checkpointing config
1 parent 8298cb2 commit d2e159e

16 files changed

+193
-84
lines changed

src/main/resources/application.conf

+1
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ mantis {
242242
# If false then that 20% gets burned
243243
# Doesn't have any effect is ecip1098 is not yet activated
244244
treasury-opt-out = false
245+
245246
}
246247

247248
# This is the section dedicated to Ethash mining.

src/main/resources/chains/etc-chain.conf

+3
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,7 @@
154154
"enode://715171f50508aba88aecd1250af392a45a330af91d7b90701c436b618c86aaa1589c9184561907bebbb56439b8f8787bc01f49a7c77276c58c1b09822d75e8e8@52.231.165.108:30303", // bootnode-azure-koreasouth-001
155155
"enode://5d6d7cd20d6da4bb83a1d28cadb5d409b64edf314c0335df658c1a54e32c7c4a7ab7823d57c39b6a757556e68ff1df17c748b698544a55cb488b52479a92b60f@104.42.217.25:30303" // bootnode-azure-westus-001
156156
]
157+
158+
# List of hex encoded public keys of Checkpoint Authorities
159+
checkpoint-public-keys = []
157160
}

src/main/resources/chains/testnet-internal-chain.conf

+3
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,7 @@
127127

128128
# Set of initial nodes
129129
bootstrap-nodes = []
130+
131+
# List of hex encoded public keys of Checkpoint Authorities
132+
checkpoint-public-keys = []
130133
}

src/main/scala/io/iohk/ethereum/consensus/ConsensusConfig.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package io.iohk.ethereum.consensus
22

33
import akka.util.ByteString
4-
import com.typesafe.config.{Config TypesafeConfig}
4+
import com.typesafe.config.{Config => TypesafeConfig}
55
import io.iohk.ethereum.consensus.validators.BlockHeaderValidator
66
import io.iohk.ethereum.domain.Address
77
import io.iohk.ethereum.nodebuilder.ShutdownHookBuilder
88
import io.iohk.ethereum.utils.Logger
99

10-
1110
/**
1211
* Provides generic consensus configuration. Each consensus protocol implementation
1312
* will use its own specific configuration as well.

src/main/scala/io/iohk/ethereum/crypto/ECDSASignature.scala

+21
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,24 @@ case class ECDSASignature(r: BigInt, s: BigInt, v: Byte) {
137137
def publicKey(message: ByteString): Option[ByteString] =
138138
ECDSASignature.recoverPubBytes(r, s, v, None, message.toArray[Byte]).map(ByteString(_))
139139
}
140+
141+
object ECDSASignatureImplicits {
142+
143+
import io.iohk.ethereum.rlp.RLPImplicitConversions._
144+
import io.iohk.ethereum.rlp.RLPImplicits._
145+
import io.iohk.ethereum.rlp._
146+
147+
implicit val ecdsaSignatureDec: RLPDecoder[ECDSASignature] = new RLPDecoder[ECDSASignature] {
148+
override def decode(rlp: RLPEncodeable): ECDSASignature = rlp match {
149+
case RLPList(r, s, v) => ECDSASignature(r: ByteString, s: ByteString, v)
150+
case _ => throw new RuntimeException("Cannot decode ECDSASignature")
151+
}
152+
}
153+
154+
implicit class ECDSASignatureEnc(ecdsaSignature: ECDSASignature) extends RLPSerializable {
155+
override def toRLPEncodable: RLPEncodeable = {
156+
RLPList(ecdsaSignature.r, ecdsaSignature.s, ecdsaSignature.v)
157+
}
158+
}
159+
160+
}

src/main/scala/io/iohk/ethereum/db/storage/BlockBodiesStorage.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package io.iohk.ethereum.db.storage
33
import java.nio.ByteBuffer
44

55
import akka.util.ByteString
6-
import boopickle.Default.{ Pickle, Unpickle }
6+
import boopickle.Default.{Pickle, Unpickle}
77
import io.iohk.ethereum.crypto.ECDSASignature
88
import io.iohk.ethereum.db.dataSource.DataSource
99
import io.iohk.ethereum.db.storage.BlockBodiesStorage.BlockBodyHash
10-
import io.iohk.ethereum.domain.{ Address, BlockHeader, BlockBody, SignedTransaction, Transaction }
10+
import io.iohk.ethereum.domain.{Address, BlockBody, BlockHeader, Checkpoint, SignedTransaction, Transaction}
1111
import io.iohk.ethereum.utils.ByteUtils.compactPickledBytes
1212

1313
/**
@@ -38,6 +38,7 @@ object BlockBodiesStorage {
3838
transformPickler[Address, ByteString](bytes => Address(bytes))(address => address.bytes)
3939
implicit val transactionPickler: Pickler[Transaction] = generatePickler[Transaction]
4040
implicit val ecdsaSignaturePickler: Pickler[ECDSASignature] = generatePickler[ECDSASignature]
41+
implicit val checkpointPickler: Pickler[Checkpoint] = generatePickler[Checkpoint]
4142
implicit val signedTransactionPickler: Pickler[SignedTransaction] = transformPickler[SignedTransaction, (Transaction, ECDSASignature)]
4243
{ case (tx, signature) => new SignedTransaction(tx, signature) }{ stx => (stx.tx, stx.signature)}
4344

src/main/scala/io/iohk/ethereum/db/storage/BlockHeadersStorage.scala

+6-47
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package io.iohk.ethereum.db.storage
33
import java.nio.ByteBuffer
44

55
import akka.util.ByteString
6-
import boopickle.Default.{ Pickle, Unpickle }
6+
import boopickle.Default.{Pickle, Unpickle}
7+
import io.iohk.ethereum.crypto.ECDSASignature
78
import io.iohk.ethereum.db.dataSource.DataSource
89
import io.iohk.ethereum.db.storage.BlockHeadersStorage.BlockHeaderHash
9-
import io.iohk.ethereum.domain.BlockHeader
10+
import io.iohk.ethereum.domain.{BlockHeader, Checkpoint}
1011
import io.iohk.ethereum.utils.ByteUtils.compactPickledBytes
1112

1213
/**
@@ -31,53 +32,11 @@ class BlockHeadersStorage(val dataSource: DataSource) extends TransactionalKeyVa
3132

3233
object BlockHeadersStorage {
3334
type BlockHeaderHash = ByteString
34-
/** The following types are [[io.iohk.ethereum.domain.BlockHeader]] param types (in exact order).
35-
*
36-
* Mentioned params:
37-
* parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot, logsBloom,
38-
* difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce.
39-
*/
40-
type BlockHeaderBody = (
41-
ByteString,
42-
ByteString,
43-
ByteString,
44-
ByteString,
45-
ByteString,
46-
ByteString,
47-
ByteString,
48-
BigInt,
49-
BigInt,
50-
BigInt,
51-
BigInt,
52-
Long,
53-
ByteString,
54-
ByteString,
55-
ByteString,
56-
Option[Boolean]
57-
)
5835

5936
import boopickle.DefaultBasic._
6037

6138
implicit val byteStringPickler: Pickler[ByteString] = transformPickler[ByteString, Array[Byte]](ByteString(_))(_.toArray[Byte])
62-
implicit val blockHeaderPickler: Pickler[BlockHeader] = transformPickler[BlockHeader, BlockHeaderBody]
63-
{ case (ph, oh, b, sr, txr, rr, lb, d, no, gl, gu, ut, ed, mh, n, oo) =>
64-
new BlockHeader(ph, oh, b, sr, txr, rr, lb, d, no, gl, gu, ut, ed, mh, n, oo)
65-
}{ blockHeader => (
66-
blockHeader.parentHash,
67-
blockHeader.ommersHash,
68-
blockHeader.beneficiary,
69-
blockHeader.stateRoot,
70-
blockHeader.transactionsRoot,
71-
blockHeader.receiptsRoot,
72-
blockHeader.logsBloom,
73-
blockHeader.difficulty,
74-
blockHeader.number,
75-
blockHeader.gasLimit,
76-
blockHeader.gasUsed,
77-
blockHeader.unixTimestamp,
78-
blockHeader.extraData,
79-
blockHeader.mixHash,
80-
blockHeader.nonce,
81-
blockHeader.treasuryOptOut
82-
)}
39+
implicit val ecdsaSignaturePickler: Pickler[ECDSASignature] = generatePickler[ECDSASignature]
40+
implicit val checkpointPickler: Pickler[Checkpoint] = generatePickler[Checkpoint]
41+
implicit val blockHeaderPickler: Pickler[BlockHeader] = generatePickler[BlockHeader]
8342
}

src/main/scala/io/iohk/ethereum/domain/BlockBody.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ object BlockBody {
6565
rlpEncodableToBlockBody(
6666
rlpEncodeable,
6767
rlp => SignedTransactionRlpEncodableDec(rlp).toSignedTransaction,
68-
rlp => BlockheaderEncodableDec(rlp).toBlockHeader
68+
rlp => BlockHeaderDec(rlp).toBlockHeader
6969
)
7070

7171
}

src/main/scala/io/iohk/ethereum/domain/BlockHeader.scala

+71-27
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ package io.iohk.ethereum.domain
22

33
import akka.util.ByteString
44
import io.iohk.ethereum.crypto.kec256
5-
import io.iohk.ethereum.rlp.RLPImplicitConversions._
6-
import io.iohk.ethereum.rlp.RLPImplicits._
7-
import io.iohk.ethereum.rlp.{RLPEncodeable, RLPList, RLPSerializable, rawDecode, encode => rlpEncode}
5+
import io.iohk.ethereum.rlp.{RLPDecoder, RLPEncodeable, RLPEncoder, RLPList, RLPSerializable, rawDecode, encode => rlpEncode}
86
import org.bouncycastle.util.encoders.Hex
97

108
case class BlockHeader(
@@ -23,7 +21,8 @@ case class BlockHeader(
2321
extraData: ByteString,
2422
mixHash: ByteString,
2523
nonce: ByteString,
26-
treasuryOptOut: Option[Boolean]) {
24+
treasuryOptOut: Option[Boolean],
25+
checkpoint: Option[Checkpoint] = None) {
2726

2827
override def toString: String = {
2928
s"""BlockHeader {
@@ -43,6 +42,7 @@ case class BlockHeader(
4342
|mixHash: ${Hex.toHexString(mixHash.toArray[Byte])}
4443
|nonce: ${Hex.toHexString(nonce.toArray[Byte])},
4544
|treasuryOptOut: $treasuryOptOut
45+
|withCheckpoint: ${checkpoint.isDefined}
4646
|}""".stripMargin
4747
}
4848

@@ -54,72 +54,116 @@ case class BlockHeader(
5454

5555
lazy val hashAsHexString: String = Hex.toHexString(hash.toArray)
5656

57+
val hasCheckpoint: Boolean = checkpoint.isDefined
58+
5759
def idTag: String =
5860
s"$number: $hashAsHexString"
5961
}
6062

6163
object BlockHeader {
6264

65+
import Checkpoint._
66+
import io.iohk.ethereum.rlp.RLPImplicitConversions._
67+
import io.iohk.ethereum.rlp.RLPImplicits._
68+
69+
private implicit val checkpointOptionDecoder = implicitly[RLPDecoder[Option[Checkpoint]]]
70+
private implicit val checkpointOptionEncoder = implicitly[RLPEncoder[Option[Checkpoint]]]
71+
6372
val emptyOmmerHash = ByteString(Hex.decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"))
6473

6574
def getEncodedWithoutNonce(blockHeader: BlockHeader): Array[Byte] = {
6675
val rlpEncoded = blockHeader.toRLPEncodable match {
67-
case rlpList: RLPList if blockHeader.treasuryOptOut.isEmpty =>
68-
// Pre ECIP1098 block
69-
RLPList(rlpList.items.dropRight(2): _*)
76+
case rlpList: RLPList if blockHeader.checkpoint.isDefined =>
77+
// post ECIP1098 & ECIP1097 block
78+
val rlpItemsWithoutNonce = rlpList.items.dropRight(4) ++ rlpList.items.takeRight(2)
79+
RLPList(rlpItemsWithoutNonce: _*)
7080

7181
case rlpList: RLPList if blockHeader.treasuryOptOut.isDefined =>
72-
// Post ECIP1098 block
82+
// Post ECIP1098 block without checkpoint
7383
val rlpItemsWithoutNonce = rlpList.items.dropRight(3) :+ rlpList.items.last
7484
RLPList(rlpItemsWithoutNonce: _*)
7585

86+
case rlpList: RLPList if blockHeader.treasuryOptOut.isEmpty =>
87+
// Pre ECIP1098 & ECIP1097 block
88+
RLPList(rlpList.items.dropRight(2): _*)
89+
7690
case _ => throw new Exception("BlockHeader cannot be encoded without nonce and mixHash")
7791
}
7892
rlpEncode(rlpEncoded)
7993
}
8094

8195
implicit class BlockHeaderEnc(blockHeader: BlockHeader) extends RLPSerializable {
96+
private def encodeOptOut(definedOptOut: Boolean) = {
97+
val encodedOptOut = if(definedOptOut) 1 else 0
98+
RLPList(encodedOptOut)
99+
}
82100
override def toRLPEncodable: RLPEncodeable = {
83101
import blockHeader._
84-
treasuryOptOut match {
85-
case Some(definedOptOut) =>
86-
// Post ECIP1098 block, whole block is encoded
87-
val encodedOptOut = if(definedOptOut) 1 else 0
102+
(treasuryOptOut, checkpoint) match {
103+
case (Some(definedOptOut), Some(_)) =>
104+
// Post ECIP1098 & ECIP1097 block, block with treasury enabled and checkpoint is encoded
105+
RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
106+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, encodeOptOut(definedOptOut), checkpoint)
88107

108+
case (Some(definedOptOut), None) =>
109+
// Post ECIP1098 block, Pre ECIP1097 or without checkpoint, block with treasury enabled is encoded
89110
RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
90-
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(encodedOptOut))
111+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, encodeOptOut(definedOptOut))
91112

92-
case None =>
93-
// Pre ECIP1098 block, encoding works as if optOut field wasn't defined for backwards compatibility
113+
case (None, Some(_)) =>
114+
// Post ECIP1097 block with checkpoint, treasury disabled, block with checkpoint is encoded
115+
RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
116+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(), checkpoint)
117+
118+
case _ =>
119+
// Pre ECIP1098 and ECIP1097 block, encoding works as if optOut and checkpoint fields weren't defined for backwards compatibility
94120
RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
95121
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce)
96122
}
97123
}
98124
}
99125

100-
implicit class BlockheaderDec(val bytes: Array[Byte]) extends AnyVal {
101-
def toBlockHeader: BlockHeader = BlockheaderEncodableDec(rawDecode(bytes)).toBlockHeader
126+
implicit class BlockHeaderByteArrayDec(val bytes: Array[Byte]) extends AnyVal {
127+
def toBlockHeader: BlockHeader = BlockHeaderDec(rawDecode(bytes)).toBlockHeader
102128
}
103129

104-
implicit class BlockheaderEncodableDec(val rlpEncodeable: RLPEncodeable) extends AnyVal {
130+
implicit class BlockHeaderDec(val rlpEncodeable: RLPEncodeable) extends AnyVal {
131+
private def decodeOptOut(encodedOptOut: RLPEncodeable): Option[Boolean] = {
132+
val booleanOptOut = {
133+
if ((encodedOptOut: Int) == 1) true
134+
else if ((encodedOptOut: Int) == 0) false
135+
else throw new Exception("BlockHeader cannot be decoded with an invalid opt-out")
136+
}
137+
Some(booleanOptOut)
138+
}
105139
def toBlockHeader: BlockHeader = {
106140
rlpEncodeable match {
107141
case RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
108-
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce) =>
109-
// Pre ECIP1098 block, encoding works as if optOut field wasn't defined for backwards compatibility
142+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(encodedOptOut), encodedCheckpoint) =>
143+
// Post ECIP1098 & ECIP1097 block with checkpoint, whole block is encoded
144+
BlockHeader(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
145+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce,
146+
decodeOptOut(encodedOptOut), checkpointOptionDecoder.decode(encodedCheckpoint))
147+
148+
case RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
149+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(), encodedCheckpoint) =>
150+
// Post ECIP1098 & ECIP1097 block with checkpoint and treasury disabled
110151
BlockHeader(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
111-
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, None)
152+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, None, checkpointOptionDecoder.decode(encodedCheckpoint))
153+
112154

113155
case RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
114156
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(encodedOptOut)) =>
115-
// Post ECIP1098 block, whole block is encoded
116-
val booleanOptOut =
117-
if ((encodedOptOut: Int) == 1) true
118-
else if ((encodedOptOut: Int) == 0) false
119-
else throw new Exception("BlockHeader cannot be decoded with an invalid opt-out")
157+
// Post ECIP1098 block without checkpoint
158+
BlockHeader(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
159+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, decodeOptOut(encodedOptOut))
120160

161+
162+
case RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
163+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce) =>
164+
// Pre ECIP1098 and ECIP1097 block, decoding works as if optOut and checkpoint fields weren't defined for backwards compatibility
121165
BlockHeader(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
122-
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, Some(booleanOptOut))
166+
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, None, None)
123167

124168
case _ =>
125169
throw new Exception("BlockHeader cannot be decoded")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.iohk.ethereum.domain
2+
3+
import io.iohk.ethereum.crypto.ECDSASignature
4+
import io.iohk.ethereum.rlp._
5+
6+
case class Checkpoint(signatures: Seq[ECDSASignature])
7+
8+
object Checkpoint {
9+
10+
import io.iohk.ethereum.crypto.ECDSASignatureImplicits._
11+
12+
implicit val checkpointRLPEncoder: RLPEncoder[Checkpoint] = { checkpoint =>
13+
RLPList(checkpoint.signatures.map(_.toRLPEncodable): _*)
14+
}
15+
16+
implicit val checkpointRLPDecoder: RLPDecoder[Checkpoint] = {
17+
case signatures: RLPList =>
18+
Checkpoint(
19+
signatures.items.map(ecdsaSignatureDec.decode)
20+
)
21+
case _ => throw new RuntimeException("Cannot decode Checkpoint")
22+
}
23+
24+
def empty: Checkpoint = Checkpoint(Nil)
25+
}

src/main/scala/io/iohk/ethereum/rlp/RLPImplicits.scala

+11
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,15 @@ object RLPImplicits {
107107
}
108108
}
109109

110+
implicit def optionEnc[T](implicit enc: RLPEncoder[T]): RLPEncoder[Option[T]] = {
111+
case None => RLPList()
112+
case Some(value) => RLPList(enc.encode(value))
113+
}
114+
115+
implicit def optionDec[T](implicit dec: RLPDecoder[T]): RLPDecoder[Option[T]] = {
116+
case RLPList(value) => Some(dec.decode(value))
117+
case RLPList() => None
118+
case rlp => throw RLPException(s"${rlp} should be a list with 1 or 0 elements")
119+
}
120+
110121
}

0 commit comments

Comments
 (0)