Skip to content

[ETCM-366] White listing consensus#797

Merged
ntallar merged 12 commits intodevelopfrom
etcm-366/consensus-white-listing
Nov 25, 2020
Merged

[ETCM-366] White listing consensus#797
ntallar merged 12 commits intodevelopfrom
etcm-366/consensus-white-listing

Conversation

@KonradStaniec
Copy link
Copy Markdown
Contributor

Description

Non-standard ethereum PoW consensus protocol, which allows restricting list of possible miners.
Main differences from basic PoW consensus protocol:

Each miner, signs header data before mining i.e prepared header without mixHash and Nonce, and appends this
signature to blockheader.extraData field. Only such prepared header is mined upon.

Each validator, checks (in addition to standard blockheader validations):

  • if blockheader.extraData field has at most 97 bytes length (32 bytes of standard extraData + 65 bytes for ECDSA signature
  • if signature is a valid signature over all blockheader data except: mixHash, Nonce, last 65 bytes of extraData field (those bytes are signature itself)
  • if public key recovered from correct signature is contained within allowedMinersPublicKeys set defined for given chain

@KonradStaniec KonradStaniec requested a review from kapke November 17, 2020 10:49
Copy link
Copy Markdown
Contributor

@kapke kapke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly minor comments. Overall looks good!

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

//TODO refactor configs to avoid possibility of running mocked or restricted-ethash consensus on real network like ETC or Mordor
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JIRA ticket maybe?

blockchainConfig = blockchainConfig,
consensusConfig = config.generic,
blockPreparator = blockPreparator,
difficultyCalculator,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor - use named parameters all the way down?

blockchainConfig = blockchainConfig,
consensusConfig = config.generic,
blockPreparator = blockPreparator,
difficultyCalculator
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

pubKeyBytes <- sig.publicKey(headerHash)
} yield ByteString.fromArrayUnsafe(pubKeyBytes)

maybePubKey.fold(false)(pubKey => allowedMiners.contains(pubKey))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor - .exists would do

def validateSignature(blockHeader: BlockHeader, allowedMiners: Set[ByteString]): Boolean = {
val signature = blockHeader.extraData.takeRight(ECDSASignature.EncodedLength)
val blockHeaderWithoutSig =
blockHeader.copy(extraData = blockHeader.extraData.dropRight(ECDSASignature.EncodedLength))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these .copy calls in this file be methods on BlockHeader?

with ObjectGenerators
with SecureRandomBuilder {
"RestrictedEthashSigner" should "sign and validate correct header" in {
forAll(blockHeaderGen, genKey(secureRandom)) { (header, key) =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should similar tests be written against RestrictedEthashHeaderValidator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh i am not sure that is possible (or desirable) as RestrictedEthashBlockHeaderValidator inherits from EthashBlockHeaderValidator which mean method validate validates proof of work therefore to property test it one should mine valid blocks which would take too much time.

Thats why I had split that way:

  • property test for signing and validating to test various different inputs
  • RestrictedEthashBlockHeaderValidatorSpec which tests different if-else parts from RestrictedEthashBlockHeaderValidator plus the case which test against someone who would try to re-sign header after pow is mined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the BlockHeaderValidatorSpec we used:

class BlockValidatorWithPowMocked(blockchainConfig: BlockchainConfig)
      extends BlockHeaderValidatorSkeleton(blockchainConfig) {
    override protected def difficulty: DifficultyCalculator = difficultyCalculator

    override def validateEvenMore(
        blockHeader: BlockHeader,
        parentHeader: BlockHeader
    ): Either[BlockHeaderError, BlockHeaderValid] = Right(BlockHeaderValid)
  }

Something like that might be useful for what you are saying

"RestrictedEthashSigner" should "sign and validate correct header" in {
forAll(blockHeaderGen, genKey(secureRandom)) { (header, key) =>
val signedHeader = RestrictedEthashSigner.signHeader(header, key)
val keyAsBytes = ByteString.fromArrayUnsafe(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io.iohk.ethereum.crypto#keyPairToByteArrays and friends instead?


it should "fail to validate header with invalid key" in new TestSetup {
val allowedKey = crypto.generateKeyPair(secureRandom)
val keyBytes = ByteString.fromArrayUnsafe(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

it should "fail to validate header signed with wrong key" in {
forAll(blockHeaderGen, genKey(secureRandom), genKey(secureRandom)) { (header, correctKey, wrongKey) =>
val signedHeader = RestrictedEthashSigner.signHeader(header, correctKey)
val wrongKeyAsBytes = ByteString.fromArrayUnsafe(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


it should "fail to validate header re-signed by valid signer" in new TestSetup {
val allowedKey = crypto.generateKeyPair(secureRandom)
val keyBytes = ByteString.fromArrayUnsafe(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Simplify RestrictedEthash validator
Few re-namings
Use crypto.utils instead of manual key conversions
Copy link
Copy Markdown
Contributor

@kapke kapke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Copy Markdown

@ntallar ntallar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only some minor comments, worked well while testing it! Will take a look at the tests next week

Comment thread src/main/resources/chains/etc-chain.conf Outdated
Copy link
Copy Markdown

@ntallar ntallar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a minor comment, apart from which LGTM!

with ObjectGenerators
with SecureRandomBuilder {
"RestrictedEthashSigner" should "sign and validate correct header" in {
forAll(blockHeaderGen, genKey(secureRandom)) { (header, key) =>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the BlockHeaderValidatorSpec we used:

class BlockValidatorWithPowMocked(blockchainConfig: BlockchainConfig)
      extends BlockHeaderValidatorSkeleton(blockchainConfig) {
    override protected def difficulty: DifficultyCalculator = difficultyCalculator

    override def validateEvenMore(
        blockHeader: BlockHeader,
        parentHeader: BlockHeader
    ): Either[BlockHeaderError, BlockHeaderValid] = Right(BlockHeaderValid)
  }

Something like that might be useful for what you are saying

Remove allowed keys option from etc-chain.conf
Add additional validation error
Add comments to special headers in test
@KonradStaniec KonradStaniec force-pushed the etcm-366/consensus-white-listing branch from 71dbcd5 to 78209f3 Compare November 25, 2020 09:13
Copy link
Copy Markdown

@ntallar ntallar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

 .\\            //.
. \ \          / /.
.\  ,\     /` /,.-
 -.   \  /'/ /  .
 ` -   `-'  \  -
   '.       /.\`
      -    .-
      :`//.'
      .`.'
      .' BP 

@ntallar ntallar merged commit dbbd199 into develop Nov 25, 2020
@ntallar ntallar deleted the etcm-366/consensus-white-listing branch November 25, 2020 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants