Skip to content

Commit

Permalink
Merge #3338
Browse files Browse the repository at this point in the history
3338: Fix generation of bonds and wallets file r=nzpr a=nzpr

Some old PR refactoring config e2bbe2f broken autogeneration of bonds file and made node require wallets file without any fallback.

This PR fixes user experience.

**The rule for bonds:** if no bonds file at path specified (or default `genesis_path/bonds.txt` if nothing specified) does not exist - node will generate one for user. If bonds file exists but is empty node will report error and exit.
**The rule for wallets:** if no wallets file - node reports that genesis won't have any genesis vault. No exception possibly thrown. Empty wallets file is allowed.

closes #3008

Co-authored-by: nutzipper <1746367+nzpr@users.noreply.github.com>
  • Loading branch information
bors[bot] and nzpr committed Mar 3, 2021
2 parents 30355a3 + cf86440 commit b159823
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 104 deletions.
4 changes: 2 additions & 2 deletions casper/src/main/scala/coop/rchain/casper/CasperConf.scala
Expand Up @@ -27,8 +27,8 @@ final case class CasperConf(

final case class GenesisBlockData(
genesisDataDir: Path,
bondsFile: Option[String],
walletsFile: Option[String],
bondsFile: String,
walletsFile: String,
bondMinimum: Long,
bondMaximum: Long,
epochLength: Int,
Expand Down
Expand Up @@ -59,10 +59,10 @@ object ApproveBlockProtocol {
)

def of[F[_]: Sync: Concurrent: RaiseIOError: CommUtil: Log: EventLog: Time: Metrics: RuntimeManager: LastApprovedBlock](
maybeBondsPath: Option[String],
bondsPath: String,
autogenShardSize: Int,
genesisPath: Path,
maybeVaultsPath: Option[String],
vaultsPath: String,
minimumBond: Long,
maximumBond: Long,
epochLength: Int,
Expand All @@ -78,12 +78,10 @@ object ApproveBlockProtocol {
now <- Time[F].currentMillis
timestamp = deployTimestamp.getOrElse(now)

vaults <- VaultParser.parse[F](maybeVaultsPath, genesisPath.resolve("wallets.txt"))
vaults <- VaultParser.parse[F](vaultsPath)
bonds <- BondsParser.parse[F](
maybeBondsPath,
genesisPath.resolve("bonds.txt"),
autogenShardSize,
genesisPath
bondsPath,
autogenShardSize
)

genesisBlock <- if (bonds.size <= requiredSigs)
Expand Down
Expand Up @@ -145,17 +145,11 @@ object CasperLaunch {
timestamp <- conf.genesisBlockData.deployTimestamp.fold(Time[F].currentMillis)(_.pure[F])
bonds <- BondsParser.parse[F](
conf.genesisBlockData.bondsFile,
conf.genesisBlockData.genesisDataDir.resolve("bonds.txt"),
conf.genesisCeremony.autogenShardSize,
conf.genesisBlockData.genesisDataDir
conf.genesisCeremony.autogenShardSize
)

validatorId <- ValidatorIdentity.fromPrivateKeyWithLogging[F](conf.validatorPrivateKey)
vaults <- VaultParser.parse(
conf.genesisBlockData.walletsFile
.map(Paths.get(_))
.getOrElse(conf.genesisBlockData.genesisDataDir.resolve("wallets.txt"))
)
vaults <- VaultParser.parse(conf.genesisBlockData.walletsFile)
bap <- BlockApproverProtocol.of(
validatorId.get,
timestamp,
Expand Down
65 changes: 34 additions & 31 deletions casper/src/main/scala/coop/rchain/casper/util/BondsParser.scala
Expand Up @@ -25,6 +25,16 @@ object BondsParser {
SourceIO
.open[F](bondsPath)
.use(_.getLines)
.flatTap(
bondsStrs =>
Sync[F]
.raiseError(
new Exception(
s"BONDS FILE $bondsPath IS EMPTY. Please fill it or remove so populated bonds file is created on startup."
)
)
.whenA(bondsStrs.isEmpty)
)
.map { lines =>
Try {
lines
Expand All @@ -40,52 +50,45 @@ object BondsParser {
bonds.toList
.traverse_ {
case (pk, stake) =>
Log[F].info(s"Parsed validator ${Base16.encode(pk.bytes)} with bond $stake")
Log[F].info(s"Bond loaded ${Base16.encode(pk.bytes)} => $stake")
}
.as(bonds)
case Failure(_) =>
Sync[F].raiseError(new Exception(s"Bonds file $bondsPath cannot be parsed"))
case Failure(e) =>
Sync[F].raiseError(
new Exception(s"FAILED PARSING BONDS FILE $bondsPath: ${e}")
)
}

def parse[F[_]: Sync: Log: RaiseIOError](
maybeBondsPath: Option[String],
defaultBondsPath: Path,
autogenShardShize: Int,
genesisPath: Path
): F[Map[PublicKey, Long]] =
maybeBondsPath match {
case Some(bondsPathStr) =>
val bondsPath = Paths.get(bondsPathStr)
exists(bondsPath).ifM(
parse(bondsPath),
Sync[F].raiseError(new Exception(s"Specified bonds file $bondsPath does not exist"))
)
case None =>
exists(defaultBondsPath).ifM(
Log[F].info(s"Using default file $defaultBondsPath") >> parse(defaultBondsPath),
Log[F].warn(
s"Bonds file was not specified and default bonds file does not exist. Falling back on generating random validators."
) >> newValidators[F](autogenShardShize, genesisPath)
)
}
bondsPathStr: String,
autogenShardSize: Int
): F[Map[PublicKey, Long]] = {
val bondsPath = Paths.get(bondsPathStr)
exists(bondsPath).ifM(
Log[F].info(s"Parsing bonds file ${bondsPath}.") >> parse(bondsPath),
Log[F].warn(s"BONDS FILE NOT FOUND: ${bondsPath}. Creating file with random bonds.") >>
newValidators[F](autogenShardSize, Path.of(bondsPathStr).toAbsolutePath)
)
}

private def newValidators[F[_]: Monad: Sync: Log](
autogenShardSize: Int,
genesisPath: Path
bondsFilePath: Path
): F[Map[PublicKey, Long]] = {
val keys = Vector.fill(autogenShardSize)(Secp256k1.newKeyPair)
val (_, pubKeys) = keys.unzip
val bonds = pubKeys.zipWithIndex.toMap.mapValues(_.toLong + 1L)
val genBondsFile = genesisPath.resolve(s"bonds.txt").toFile
val genesisFolder = bondsFilePath.getParent
val keys = Vector.fill(autogenShardSize)(Secp256k1.newKeyPair)
val (_, pubKeys) = keys.unzip
val bonds = pubKeys.zipWithIndex.toMap.mapValues(_.toLong + 1L)
val genBondsFile = bondsFilePath.toFile

val skFiles =
Sync[F].delay(genesisPath.toFile.mkdir()) >>
Sync[F].delay(genesisFolder.toFile.mkdirs()) >>
Sync[F].delay {
keys.foreach { //create files showing the secret key for each public key
case (sec, pub) =>
val sk = Base16.encode(sec.bytes)
val pk = Base16.encode(pub.bytes)
val skFile = genesisPath.resolve(s"$pk.sk").toFile
val skFile = genesisFolder.resolve(s"$pk.sk").toFile
val printer = new PrintWriter(skFile)
printer.println(sk)
printer.close()
Expand All @@ -99,7 +102,7 @@ object BondsParser {
_ <- bonds.toList.traverse_ {
case (pub, stake) =>
val pk = Base16.encode(pub.bytes)
Log[F].info(s"Created validator $pk with bond $stake") >> Sync[F].delay(
Log[F].info(s"Bond generated $pk => $stake") >> Sync[F].delay(
printer.println(s"$pk $stake")
)
}
Expand Down
62 changes: 23 additions & 39 deletions casper/src/main/scala/coop/rchain/casper/util/VaultParser.scala
@@ -1,47 +1,30 @@
package coop.rchain.casper.util

import java.io.FileNotFoundException
import java.nio.file.{Path, Paths}

import cats.Monad
import cats.effect.Sync
import cats.implicits._
import coop.rchain.blockstorage.util.io.IOError.RaiseIOError
import coop.rchain.blockstorage.util.io.{exists, FileNotFound, SourceIO}
import coop.rchain.blockstorage.util.io.{exists, SourceIO}
import coop.rchain.casper.genesis.contracts.Vault
import coop.rchain.rholang.interpreter.util.RevAddress
import coop.rchain.shared.Log

import java.nio.file.{Path, Paths}
import scala.util.{Failure, Success, Try}

object VaultParser {
def parse[F[_]: Sync: Log: RaiseIOError](
maybeVaultPath: Option[String],
defaultVaultPath: Path
): F[Seq[Vault]] =
maybeVaultPath match {
case Some(vaultsPathStr) =>
val vaultsPath = Paths.get(vaultsPathStr)
exists(vaultsPath).ifM(
parse[F](vaultsPath),
RaiseIOError[F].raise(
FileNotFound(
new FileNotFoundException(s"Specified vaults file $vaultsPath does not exist")
)
)
)
case None =>
exists(defaultVaultPath).ifM(
Log[F].info(s"Using default file $defaultVaultPath") >> parse[F](
defaultVaultPath
),
Log[F]
.warn(
"No vaults file specified and no default file found. No vaults will exist at genesis."
)
.map(_ => Seq.empty[Vault])
)
}
vaultsPathStr: String
): F[Seq[Vault]] = {
val vaultsPath = Paths.get(vaultsPathStr)
exists(vaultsPath).ifM(
Log[F]
.info(s"Parsing wallets file ${vaultsPath}.") >>
parse[F](vaultsPath),
Log[F]
.warn(s"WALLETS FILE NOT FOUND: ${vaultsPath}. No vaults will be put in genesis block.")
.as(Seq.empty[Vault])
)
}

def parse[F[_]: Sync: Log: RaiseIOError](vaultsPath: Path): F[Seq[Vault]] =
for {
Expand All @@ -51,17 +34,18 @@ object VaultParser {
.adaptError {
case ex: Throwable =>
new RuntimeException(
s"Failed to read ${vaultsPath.toAbsolutePath} for reason: ${ex.getMessage}"
s"FAILED TO READ ${vaultsPath.toAbsolutePath}: ${ex.getMessage}"
)
}
vaults <- lines.traverse(parseLine[F])
} yield vaults

private def parseLine[F[_]: Sync: Log: RaiseIOError](line: String): F[Vault] =
Sync[F].fromEither(
fromLine(line)
.leftMap(errMsg => new RuntimeException(s"Error in parsing vaults file: $errMsg"))
)
Sync[F]
.fromEither(
fromLine(line)
.leftMap(errMsg => new RuntimeException(s"FAILED PARSING WALLETS FILE: $errMsg"))
) <* Log[F].info(s"Wallet loaded: ${line}")

private def fromLine(line: String): Either[String, Vault] = line.split(",") match {
case Array(ethAddressString, initRevBalanceStr, _) =>
Expand All @@ -70,12 +54,12 @@ object VaultParser {
RevAddress
.fromEthAddress(ethAddressString)
.map(Vault(_, initRevBalance))
.toRight(s"Ethereum address $ethAddressString is invalid.")
.toRight(s"INVALID ETH ADDRESS while parsing wallets file: $ethAddressString")
case Failure(_) =>
Left(s"Failed to parse given initial balance $initRevBalanceStr as positive long.")
Left(s"INVALID WALLET BALANCE $initRevBalanceStr. Please put positive long.")
}

case _ => Left(s"Invalid vault specification:\n$line")
case _ => Left(s"INVALID WALLET FORMAT:\n$line")
}

}
24 changes: 11 additions & 13 deletions casper/src/test/scala/coop/rchain/casper/genesis/GenesisTest.scala
Expand Up @@ -78,14 +78,14 @@ class GenesisTest extends FlatSpec with Matchers with EitherValues with BlockDag
_ <- fromInputFiles()(runtimeManager, genesisPath, log, time)
_ = log.warns.count(
_.contains(
"Bonds file was not specified and default bonds file does not exist. Falling back on generating random validators."
"Creating file with random bonds"
)
) should be(1)
} yield log.infos.count(_.contains("Created validator")) should be(autogenShardSize)
} yield log.infos.count(_.contains("Bond generated")) should be(autogenShardSize)
}
)

it should "fail with error when bonds file does not exist" in taskTest(
it should "tell when bonds file does not exist" in taskTest(
withGenResources {
(
runtimeManager: RuntimeManager[Task],
Expand All @@ -100,9 +100,7 @@ class GenesisTest extends FlatSpec with Matchers with EitherValues with BlockDag
log,
time
).attempt
} yield genesisAttempt.left.value.getMessage should be(
"Specified bonds file not/a/real/file does not exist"
)
} yield log.warns.exists(_.contains("BONDS FILE NOT FOUND"))
}
)

Expand All @@ -128,7 +126,7 @@ class GenesisTest extends FlatSpec with Matchers with EitherValues with BlockDag
time
).attempt
} yield genesisAttempt.left.value.getMessage should include(
"misformatted.txt cannot be parsed"
"FAILED PARSING BONDS FILE"
)
}
)
Expand All @@ -152,7 +150,7 @@ class GenesisTest extends FlatSpec with Matchers with EitherValues with BlockDag
time
)
bonds = ProtoUtil.bonds(genesis)
_ = log.infos.length should be(2)
_ = log.infos.length should be(3)
result = validators
.map {
case (v, i) => Bond(ByteString.copyFrom(Base16.unsafeDecode(v)), i.toLong)
Expand Down Expand Up @@ -238,12 +236,12 @@ object GenesisTest {
): Task[BlockMessage] =
for {
timestamp <- deployTimestamp.fold(Time[Task].currentMillis)(x => x.pure[Task])
vaults <- VaultParser.parse[Task](maybeVaultsPath, genesisPath.resolve("wallets.txt"))
vaults <- VaultParser.parse[Task](
maybeVaultsPath.getOrElse(genesisPath + "/wallets.txt")
)
bonds <- BondsParser.parse[Task](
maybeBondsPath,
genesisPath.resolve("bonds.txt"),
autogenShardSize,
genesisPath
maybeBondsPath.getOrElse(genesisPath + "/bonds.txt"),
autogenShardSize
)
validators = bonds.toSeq.map(Validator.tupled)
genesisBlock <- createGenesisBlock(
Expand Down
Expand Up @@ -214,8 +214,8 @@ class ConfigMapperSpec extends FunSuite with Matchers {
),
genesisBlockData = GenesisBlockData(
genesisDataDir = Paths.get("/var/lib/rnode/genesis"),
bondsFile = Some("/var/lib/rnode/genesis/bonds1.txt"),
walletsFile = Some("/var/lib/rnode/genesis/wallets1.txt"),
bondsFile = "/var/lib/rnode/genesis/bonds1.txt",
walletsFile = "/var/lib/rnode/genesis/wallets1.txt",
bondMaximum = 111111,
bondMinimum = 111111,
epochLength = 111111,
Expand Down
Expand Up @@ -124,8 +124,8 @@ class HoconConfigurationSpec extends FunSuite with Matchers {
),
genesisBlockData = GenesisBlockData(
genesisDataDir = Paths.get("/var/lib/rnode/genesis"),
bondsFile = Some("/var/lib/rnode/genesis/bonds.txt"),
walletsFile = Some("/var/lib/rnode/genesis/wallets.txt"),
bondsFile = "/var/lib/rnode/genesis/bonds.txt",
walletsFile = "/var/lib/rnode/genesis/wallets.txt",
bondMaximum = 9223372036854775807L,
bondMinimum = 1,
epochLength = 10000,
Expand Down

0 comments on commit b159823

Please sign in to comment.