Skip to content

Commit dbbd199

Browse files
[ETCM-366] White listing consensus (#797)
1 parent b5cb34b commit dbbd199

24 files changed

+495
-31
lines changed

src/ets/scala/io/iohk/ethereum/ets/blockchain/ScenarioSetup.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package io.iohk.ethereum.ets.blockchain
22

33
import akka.util.ByteString
44
import java.util.concurrent.Executors
5+
6+
import io.iohk.ethereum.consensus.Protocol.NoAdditionalEthashData
57
import io.iohk.ethereum.consensus.ethash.EthashConsensus
68
import io.iohk.ethereum.consensus.ethash.validators.ValidatorsExecutor
79
import io.iohk.ethereum.consensus.{ConsensusConfig, FullConsensusConfig, TestConsensus, ethash}
@@ -31,7 +33,7 @@ object ScenarioSetup {
3133
blockchainConfig: BlockchainConfig,
3234
validators: ValidatorsExecutor
3335
): ethash.EthashConsensus = {
34-
val consensus = EthashConsensus(vm, blockchain, blockchainConfig, fullConfig, validators)
36+
val consensus = EthashConsensus(vm, blockchain, blockchainConfig, fullConfig, validators, NoAdditionalEthashData)
3537
consensus
3638
}
3739

src/it/scala/io/iohk/ethereum/sync/util/RegularSyncItSpecUtils.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import io.iohk.ethereum.Mocks.MockValidatorsAlwaysSucceed
77
import io.iohk.ethereum.blockchain.sync.{PeersClient, SyncProtocol}
88
import io.iohk.ethereum.blockchain.sync.regular.BlockBroadcasterActor.BroadcastBlock
99
import io.iohk.ethereum.blockchain.sync.regular.RegularSync
10+
import io.iohk.ethereum.consensus.Protocol.NoAdditionalEthashData
1011
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
1112
import io.iohk.ethereum.consensus.ethash.{EthashConfig, EthashConsensus}
1213
import io.iohk.ethereum.consensus.{ConsensusConfig, FullConsensusConfig, ethash}
@@ -45,7 +46,8 @@ object RegularSyncItSpecUtils {
4546
val specificConfig: EthashConfig = ethash.EthashConfig(config)
4647
val fullConfig = FullConsensusConfig(consensusConfig, specificConfig)
4748
val vm = VmSetup.vm(VmConfig(config), blockchainConfig, testMode = false)
48-
val consensus = EthashConsensus(vm, bl, blockchainConfig, fullConfig, ValidatorsExecutorAlwaysSucceed)
49+
val consensus =
50+
EthashConsensus(vm, bl, blockchainConfig, fullConfig, ValidatorsExecutorAlwaysSucceed, NoAdditionalEthashData)
4951
consensus
5052
}
5153

src/main/resources/application.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ mantis {
251251
# Declaring the protocol here means that a more protocol-specific configuration
252252
# is pulled from the corresponding consensus implementation.
253253
# For example, in case of ethash, a section named `ethash` is used.
254-
# Available protocols: ethash, mocked
254+
# Available protocols: ethash, mocked, restricted-ethash
255255
# In case of mocked, remember to enable qa api
256256
protocol = ethash
257257

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,8 @@
160160

161161
# Set of initial nodes
162162
bootstrap-nodes = []
163+
164+
# List of hex encoded public keys of miners which can extend chain (only used when using restricted-ethash consensus)
165+
# empty means that everybody can mine
166+
allowed-miners = []
163167
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,8 @@
144144

145145
# List of hex encoded public keys of Checkpoint Authorities
146146
checkpoint-public-keys = []
147+
148+
# List of hex encoded public keys of miners which can extend chain (only used when using restricted-ethash consensus)
149+
# empty means that everybody can mine
150+
allowed-miners = []
147151
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,8 @@
150150

151151
# List of hex encoded public keys of Checkpoint Authorities
152152
checkpoint-public-keys = []
153+
154+
# List of hex encoded public keys of miners which can extend chain (only used when using restricted-ethash consensus)
155+
# empty means that everybody can mine
156+
allowed-miners = []
153157
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.iohk.ethereum.consensus
22

3+
import io.iohk.ethereum.consensus.Protocol.{NoAdditionalEthashData, RestrictedEthashMinerData}
34
import io.iohk.ethereum.consensus.ethash.EthashConsensus
45
import io.iohk.ethereum.consensus.ethash.validators.ValidatorsExecutor
56
import io.iohk.ethereum.nodebuilder._
@@ -17,18 +18,32 @@ trait ConsensusBuilder {
1718
* [[io.iohk.ethereum.consensus.ethash.EthashConsensus EthashConsensus]],
1819
*/
1920
trait StdConsensusBuilder extends ConsensusBuilder {
20-
self: VmBuilder with BlockchainBuilder with BlockchainConfigBuilder with ConsensusConfigBuilder with Logger =>
21+
self: VmBuilder
22+
with BlockchainBuilder
23+
with BlockchainConfigBuilder
24+
with ConsensusConfigBuilder
25+
with NodeKeyBuilder
26+
with Logger =>
2127

2228
private lazy val mantisConfig = Config.config
2329

2430
private def newConfig[C <: AnyRef](c: C): FullConsensusConfig[C] =
2531
FullConsensusConfig(consensusConfig, c)
2632

33+
//TODO [ETCM-397] refactor configs to avoid possibility of running mocked or
34+
// restricted-ethash consensus on real network like ETC or Mordor
2735
protected def buildEthashConsensus(): ethash.EthashConsensus = {
2836
val specificConfig = ethash.EthashConfig(mantisConfig)
37+
2938
val fullConfig = newConfig(specificConfig)
39+
3040
val validators = ValidatorsExecutor(blockchainConfig, consensusConfig.protocol)
31-
val consensus = EthashConsensus(vm, blockchain, blockchainConfig, fullConfig, validators)
41+
42+
val additionalEthashData = consensusConfig.protocol match {
43+
case Protocol.Ethash | Protocol.MockedPow => NoAdditionalEthashData
44+
case Protocol.RestrictedEthash => RestrictedEthashMinerData(nodeKey)
45+
}
46+
val consensus = EthashConsensus(vm, blockchain, blockchainConfig, fullConfig, validators, additionalEthashData)
3247
consensus
3348
}
3449

@@ -38,7 +53,7 @@ trait StdConsensusBuilder extends ConsensusBuilder {
3853

3954
val consensus =
4055
config.protocol match {
41-
case Protocol.Ethash | Protocol.MockedPow => buildEthashConsensus()
56+
case Protocol.Ethash | Protocol.MockedPow | Protocol.RestrictedEthash => buildEthashConsensus()
4257
}
4358
log.info(s"Using '${protocol.name}' consensus [${consensus.getClass.getName}]")
4459

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ object ConsensusConfig extends Logger {
3636

3737
final val AllowedProtocols = Set(
3838
Protocol.Names.Ethash,
39-
Protocol.Names.MockedPow
39+
Protocol.Names.MockedPow,
40+
Protocol.Names.RestrictedEthash
4041
)
4142

4243
final val AllowedProtocolsError = (s: String) =>

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.iohk.ethereum.consensus
22

3+
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
4+
35
/**
46
* Enumerates the known consensus protocols that Mantis can use.
57
* For the respective implementations, see [[io.iohk.ethereum.consensus.Consensus Consensus]].
@@ -20,20 +22,38 @@ object Protocol {
2022
final val Ethash = "ethash"
2123

2224
final val MockedPow = "mocked"
25+
26+
final val RestrictedEthash = "restricted-ethash"
2327
}
2428

2529
sealed abstract class ProtocolImpl(val name: String) extends Protocol
2630

31+
/** Mocked pow consensus algorithm used for tests etc. */
32+
case object MockedPow extends ProtocolImpl(Names.MockedPow)
33+
2734
/** The standard Ethereum PoW consensus protocol. */
2835
case object Ethash extends ProtocolImpl(Names.Ethash)
2936

30-
/** Mocked pow consensus algorithm used for tests etc. */
31-
case object MockedPow extends ProtocolImpl(Names.MockedPow)
37+
/**
38+
* Non-standard ethereum PoW consensus protocol, which allows restricting list of possible miners.
39+
* Main differences from basic PoW consensus protocol:
40+
* - Each miner, signs header data before mining i.e prepared header without mixHash and Nonce, and appends this
41+
* signature to blockheader.extraData field. Only such prepared header is mined upon.
42+
* - Each validator, checks (in addition to standard blockheader validations):
43+
* a) if blockheader.extraData field has at most 97 bytes length (32 bytes of standard extraData + 65 bytes
44+
* for ECDSA signature
45+
* b) if signature is a valid signature over all blockheader data except: mixHash, Nonce, last 65 bytes of
46+
* extraData field (those bytes are signature itself)
47+
* c) if public key recovered from correct signature is contained within allowedMinersPublicKeys set defined
48+
* for given chain
49+
*/
50+
case object RestrictedEthash extends ProtocolImpl(Names.RestrictedEthash)
3251

3352
/** All the known protocols. If a protocol is not put here, then it cannot be used to run Mantis. */
3453
final val KnownProtocols = Set(
3554
Ethash,
36-
MockedPow
55+
MockedPow,
56+
RestrictedEthash
3757
)
3858

3959
final val KnownProtocolNames = KnownProtocols.map(_.name)
@@ -44,4 +64,8 @@ object Protocol {
4464
find(name).getOrElse {
4565
throw new IllegalArgumentException("Unknown protocol " + name)
4666
}
67+
68+
sealed abstract class AdditionalEthashProtocolData
69+
case object NoAdditionalEthashData extends AdditionalEthashProtocolData
70+
case class RestrictedEthashMinerData(miningNodeKey: AsymmetricCipherKeyPair) extends AdditionalEthashProtocolData
4771
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ trait StdTestConsensusBuilder
2222
with BlockchainBuilder
2323
with StorageBuilder
2424
with BlockchainConfigBuilder
25+
with NodeKeyBuilder
26+
with SecureRandomBuilder
2527
with ConsensusConfigBuilder
2628
with ShutdownHookBuilder
2729
with Logger

src/main/scala/io/iohk/ethereum/consensus/ethash/EthashConsensus.scala

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,22 @@ import java.util.concurrent.atomic.AtomicReference
66

77
import akka.actor.ActorRef
88
import akka.util.Timeout
9-
import io.iohk.ethereum.consensus.Protocol.{Ethash, MockedPow}
9+
import io.iohk.ethereum.consensus.Protocol.{
10+
AdditionalEthashProtocolData,
11+
Ethash,
12+
MockedPow,
13+
NoAdditionalEthashData,
14+
RestrictedEthash,
15+
RestrictedEthashMinerData
16+
}
1017
import io.iohk.ethereum.consensus.blocks.TestBlockGenerator
1118
import io.iohk.ethereum.consensus.difficulty.DifficultyCalculator
1219
import io.iohk.ethereum.consensus.ethash.MinerResponses.MinerNotExist
13-
import io.iohk.ethereum.consensus.ethash.blocks.{EthashBlockGenerator, EthashBlockGeneratorImpl}
20+
import io.iohk.ethereum.consensus.ethash.blocks.{
21+
EthashBlockGenerator,
22+
EthashBlockGeneratorImpl,
23+
RestrictedEthashBlockGeneratorImpl
24+
}
1425
import io.iohk.ethereum.consensus.ethash.validators.ValidatorsExecutor
1526
import io.iohk.ethereum.consensus.validators.Validators
1627
import io.iohk.ethereum.domain.BlockchainImpl
@@ -61,7 +72,7 @@ class EthashConsensus private (
6172
atomicMiner.get() match {
6273
case None =>
6374
val miner = config.generic.protocol match {
64-
case Ethash => EthashMiner(node)
75+
case Ethash | RestrictedEthash => EthashMiner(node)
6576
case MockedPow => MockedMiner(node)
6677
}
6778
atomicMiner.set(Some(miner))
@@ -175,7 +186,8 @@ object EthashConsensus {
175186
blockchain: BlockchainImpl,
176187
blockchainConfig: BlockchainConfig,
177188
config: FullConsensusConfig[EthashConfig],
178-
validators: ValidatorsExecutor
189+
validators: ValidatorsExecutor,
190+
additionalEthashProtocolData: AdditionalEthashProtocolData
179191
): EthashConsensus = {
180192

181193
val difficultyCalculator = DifficultyCalculator(blockchainConfig)
@@ -187,14 +199,28 @@ object EthashConsensus {
187199
blockchainConfig = blockchainConfig
188200
)
189201

190-
val blockGenerator = new EthashBlockGeneratorImpl(
191-
validators = validators,
192-
blockchain = blockchain,
193-
blockchainConfig = blockchainConfig,
194-
consensusConfig = config.generic,
195-
blockPreparator = blockPreparator,
196-
difficultyCalculator
197-
)
202+
val blockGenerator = additionalEthashProtocolData match {
203+
case RestrictedEthashMinerData(key) =>
204+
new RestrictedEthashBlockGeneratorImpl(
205+
validators = validators,
206+
blockchain = blockchain,
207+
blockchainConfig = blockchainConfig,
208+
consensusConfig = config.generic,
209+
blockPreparator = blockPreparator,
210+
difficultyCalc = difficultyCalculator,
211+
minerKeyPair = key
212+
)
213+
214+
case NoAdditionalEthashData =>
215+
new EthashBlockGeneratorImpl(
216+
validators = validators,
217+
blockchain = blockchain,
218+
blockchainConfig = blockchainConfig,
219+
consensusConfig = config.generic,
220+
blockPreparator = blockPreparator,
221+
difficultyCalc = difficultyCalculator
222+
)
223+
}
198224

199225
new EthashConsensus(
200226
vm = vm,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.iohk.ethereum.consensus.ethash
2+
3+
import akka.util.ByteString
4+
import io.iohk.ethereum.crypto
5+
import io.iohk.ethereum.crypto.ECDSASignature
6+
import io.iohk.ethereum.domain.BlockHeader
7+
import io.iohk.ethereum.domain.BlockHeader.getEncodedWithoutNonce
8+
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
9+
10+
object RestrictedEthashSigner {
11+
12+
def validateSignature(blockHeader: BlockHeader, allowedMiners: Set[ByteString]): Boolean = {
13+
val signature = blockHeader.extraData.takeRight(ECDSASignature.EncodedLength)
14+
val blockHeaderWithoutSig = blockHeader.dropRightNExtraDataBytes(ECDSASignature.EncodedLength)
15+
val encodedBlockHeader = getEncodedWithoutNonce(blockHeaderWithoutSig)
16+
val headerHash = crypto.kec256(encodedBlockHeader)
17+
val maybePubKey = for {
18+
sig <- ECDSASignature.fromBytes(signature)
19+
pubKeyBytes <- sig.publicKey(headerHash)
20+
} yield ByteString.fromArrayUnsafe(pubKeyBytes)
21+
22+
maybePubKey.exists(allowedMiners.contains)
23+
}
24+
25+
def signHeader(blockHeader: BlockHeader, keyPair: AsymmetricCipherKeyPair): BlockHeader = {
26+
val encoded = getEncodedWithoutNonce(blockHeader)
27+
val hash = crypto.kec256(encoded)
28+
val signed = ECDSASignature.sign(hash, keyPair)
29+
val sigBytes = signed.toBytes
30+
blockHeader.withAdditionalExtraData(sigBytes)
31+
}
32+
33+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.iohk.ethereum.consensus.ethash.blocks
2+
3+
import io.iohk.ethereum.consensus.ConsensusConfig
4+
import io.iohk.ethereum.consensus.blocks.{BlockTimestampProvider, DefaultBlockTimestampProvider, PendingBlockAndState}
5+
import io.iohk.ethereum.consensus.difficulty.DifficultyCalculator
6+
import io.iohk.ethereum.consensus.ethash.RestrictedEthashSigner
7+
import io.iohk.ethereum.consensus.ethash.validators.ValidatorsExecutor
8+
import io.iohk.ethereum.domain.{Address, Block, Blockchain, SignedTransaction}
9+
import io.iohk.ethereum.ledger.{BlockPreparator, InMemoryWorldStateProxy}
10+
import io.iohk.ethereum.utils.BlockchainConfig
11+
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
12+
13+
class RestrictedEthashBlockGeneratorImpl(
14+
validators: ValidatorsExecutor,
15+
blockchain: Blockchain,
16+
blockchainConfig: BlockchainConfig,
17+
consensusConfig: ConsensusConfig,
18+
override val blockPreparator: BlockPreparator,
19+
difficultyCalc: DifficultyCalculator,
20+
minerKeyPair: AsymmetricCipherKeyPair,
21+
blockTimestampProvider: BlockTimestampProvider = DefaultBlockTimestampProvider
22+
) extends EthashBlockGeneratorImpl(
23+
validators,
24+
blockchain,
25+
blockchainConfig,
26+
consensusConfig,
27+
blockPreparator,
28+
difficultyCalc,
29+
blockTimestampProvider
30+
) {
31+
32+
override def generateBlock(
33+
parent: Block,
34+
transactions: Seq[SignedTransaction],
35+
beneficiary: Address,
36+
ommers: Ommers,
37+
initialWorldStateBeforeExecution: Option[InMemoryWorldStateProxy]
38+
): PendingBlockAndState = {
39+
val pHeader = parent.header
40+
val blockNumber = pHeader.number + 1
41+
val parentHash = pHeader.hash
42+
43+
val validatedOmmers = validators.ommersValidator.validate(parentHash, blockNumber, ommers, blockchain) match {
44+
case Left(_) => emptyX
45+
case Right(_) => ommers
46+
}
47+
val prepared = prepareBlock(
48+
parent,
49+
transactions,
50+
beneficiary,
51+
blockNumber,
52+
blockPreparator,
53+
validatedOmmers,
54+
initialWorldStateBeforeExecution
55+
)
56+
val preparedHeader = prepared.pendingBlock.block.header
57+
val headerWithAdditionalExtraData = RestrictedEthashSigner.signHeader(preparedHeader, minerKeyPair)
58+
val modifiedPrepared = prepared.copy(pendingBlock =
59+
prepared.pendingBlock.copy(block = prepared.pendingBlock.block.copy(header = headerWithAdditionalExtraData))
60+
)
61+
62+
cache.updateAndGet { t: List[PendingBlockAndState] =>
63+
(modifiedPrepared :: t).take(blockCacheSize)
64+
}
65+
66+
modifiedPrepared
67+
}
68+
69+
}

0 commit comments

Comments
 (0)