Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix generation of bonds and wallets file #3338

Merged
merged 1 commit into from Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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