Skip to content

Instantly share code, notes, and snippets.

@matiwinnetou
Last active August 14, 2023 08:08
Show Gist options
  • Save matiwinnetou/8cf5ccff4c8a378c6b909b5c5ce6edab to your computer and use it in GitHub Desktop.
Save matiwinnetou/8cf5ccff4c8a378c6b909b5c5ce6edab to your computer and use it in GitHub Desktop.
import $ivy.`com.bloxbean.cardano:cardano-client-lib:0.5.0-alpha.4`
import $ivy.`com.bloxbean.cardano:cardano-client-backend-blockfrost:0.5.0-alpha.4`
import $ivy.`com.bloxbean.cardano:cardano-client-cip30:0.5.0-alpha.4`
import $ivy.`com.lihaoyi:requests_3:0.8.0`
import $ivy.`com.fasterxml.jackson.core:jackson-core:2.15.2`
import com.bloxbean.cardano.client.api.model.Amount
import com.bloxbean.cardano.client.crypto._
import com.bloxbean.cardano.client.account._
import com.bloxbean.cardano.client.common.model._
import com.bloxbean.cardano.client.cip.cip30._
import com.bloxbean.cardano.client.address._
import com.bloxbean.cardano.client.crypto.Blake2bUtil._
import com.bloxbean.cardano.client.common.cbor._
import com.bloxbean.cardano.client.metadata._
import com.bloxbean.cardano.client.common._
import java.math._
import java.util.Random
import com.fasterxml.jackson.databind._
import com.bloxbean.cardano.client.util.HexUtil
import com.bloxbean.cardano.client.quicktx._
import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService
import com.bloxbean.cardano.client.function.helper.SignerProviders
case class CreateEventCommand(id: String,
team: String,
allowVoteChanging: Boolean,
categoryResultsWhileVoting: Boolean,
highLevelResultsWhileVoting: Boolean,
votingEventType: String,
votingPowerAsset: Option[String] = None,
startEpoch: Option[Int] = None,
endEpoch: Option[Int] = None,
startSlot: Option[Long] = None,
endSlot: Option[Long] = None,
snapshotEpoch: Option[Int] = None,
schemaVersion: String
)
case class Proposal(id: String, name: String)
case class CreateCategoryCommand(id: String,
event: String,
gdprProtection: Boolean,
proposals: List[Proposal],
schemaVersion: String
)
val blockfrostUrl = "http://localhost:8080/api/v1/"
val blockfrostApiKey = ""
val backendService = new BFBackendService(blockfrostUrl, blockfrostApiKey)
val metadataLabel = 11113
val amountAda: Int = 1000
val eventName = "CIP-1694_Pre_Ratification_YACI"
val orgMnemonic = "ocean sad mixture disease faith once celery mind clay hidden brush brown you sponsor dawn good claim gloom market world online twist laptop thrive"
val organiserAccount = new Account(Networks.testnet(), orgMnemonic)
val mapper = new ObjectMapper()
val cip1694Event = CreateEventCommand(
id = eventName,
team = "CF",
allowVoteChanging = false,
categoryResultsWhileVoting = false,
highLevelResultsWhileVoting = false,
votingEventType = "STAKE_BASED",
votingPowerAsset = Some("ADA"),
startEpoch = Some(1),
endEpoch = Some(1000),
snapshotEpoch = Some(0),
schemaVersion = "1.0.0"
)
val yesProposal = new Proposal(
id = "e42f820f-5852-4c03-9d42-8cf4a4044a51",
name = "YES"
)
val noProposal = new Proposal(
id = "3b40644b-3f6f-4c91-945e-4d612fa4f6cf",
name = "NO"
)
val abstainProposal = new Proposal(
id = "a8f60f84-58bf-47b3-9582-5272fbdc6ff6",
name = "ABSTAIN"
)
val cip1694Category = CreateCategoryCommand(
id = "CIP-1694_Pre_Ratification",
event = eventName,
gdprProtection = false,
schemaVersion = "1.0.0",
proposals = List(yesProposal, noProposal, abstainProposal)
)
def toBigInteger(value: Boolean): BigInteger = if (value) { BigInteger.ONE } else { BigInteger.ZERO }
def topUpAccount(newAcc: Account, amount: Int): Boolean = {
val jsonTopUpPayload = s""" {
"address": \"${newAcc.baseAddress()}\",
"adaAmount": $amount
}
""".stripMargin
val r = requests.post(
"http://localhost:10000/local-cluster/api/addresses/topup",
headers = Map("Content-Type" -> "application/json"),
data = jsonTopUpPayload
)
var res = if (r.statusCode == 200) {
println(s"Topup with ${amount} for addr:${newAcc.baseAddress} success!")
true
} else {
println(s"Topup with ${amount} for addr:${newAcc.baseAddress} failed!")
false
}
println("sleeping for 1 sec")
Thread.sleep(1000L)
res
}
def randomProposal(): Proposal = {
val r = new Random()
val proposals = List(yesProposal, noProposal, abstainProposal)
val index = r.nextInt(proposals.length)
proposals(index)
}
def castVote(acc: Account, amountAda: Int): Boolean = {
val lastSlot = latestAbsoluteSlot(mapper)
println(s"slot:${lastSlot}")
println("Casting random vote for account...");
val voteId = java.util.UUID.randomUUID().toString()
val stakeAddress = acc.stakeAddress()
println("stakeAddress:" + stakeAddress)
val randomP: Proposal = randomProposal()
val stakeAddressAccount = new Address(stakeAddress);
val voteJson = s"""
{
"uri": "https://evoting.cardano.org/voltaire",
"action": "CAST_VOTE",
"actionText": "Cast Vote",
"slot": "${lastSlot}",
"data": {
"id": "${voteId}",
"address": "${stakeAddress}",
"event": "CIP-1694_Pre_Ratification_YACI",
"category": "CIP-1694_Pre_Ratification",
"proposal": "${randomP.name}",
"network": "DEV",
"votedAt": "${lastSlot}",
"votingPower": "${ADAConversionUtil.adaToLovelace(amountAda)}"
}
}
""".stripMargin
println(s"${voteJson}")
val castCoteCIP30Result = CIP30DataSigner.INSTANCE.signData(
stakeAddressAccount.getBytes(),
voteJson.getBytes("UTF-8"),
acc.stakeHdKeyPair().getPrivateKey().getKeyData(),
acc.stakeHdKeyPair().getPublicKey().getKeyData()
);
val cip30JsonNode = mapper.createObjectNode()
cip30JsonNode.put("coseSignature", castCoteCIP30Result.signature())
cip30JsonNode.put("cosePublicKey", castCoteCIP30Result.key())
val requestString = mapper.writerWithDefaultPrettyPrinter
.writeValueAsString(cip30JsonNode)
val r = requests.post(
"http://localhost:9090/api/vote/cast",
headers = Map("Content-Type" -> "application/json"),
data = requestString
)
if (r.statusCode == 200) {
println(s"VoteId: ${voteId} cast successfuly!")
true
} else {
println(s"VoteId: ${voteId} cast failed!")
false
}
}
def latestAbsoluteSlot(mapper: ObjectMapper): Long = {
val r = requests.get(
"http://localhost:8080/api/v1/blocks/latest",
headers = Map("Content-Type" -> "application/json")
)
if (r.statusCode == 200) {
val body = r.text
val tree = mapper.readTree(body);
val slot = tree.get("slot").asLong()
slot
} else {
-1
}
}
def registerEventAndCategories(organiserAccount: Account, mapper: ObjectMapper, eventName: String): Boolean = {
var latestSlot: Long = latestAbsoluteSlot(mapper)
println(s"Latest absolute slot:${latestSlot}")
if (latestSlot == -1) {
return false
}
val eventMetadataMap: MetadataMap = createEvent(cip1694Event, latestSlot)
val eventMetadata: Metadata = serialiseCIP30(organiserAccount, eventMetadataMap, "EVENT_REGISTRATION")
var eventTxData: Array[Byte] = serialiseTx(organiserAccount, eventMetadata)
var eventTxId: String = submitTx(eventTxData)
println("Event txId:" + eventTxId)
if (eventTxId != null) {
latestSlot = latestAbsoluteSlot(mapper)
println(s"Latest absolute slot:${latestSlot}")
val categoryMetadataMap: MetadataMap = createCategory(cip1694Category, latestSlot)
val categoryMetadata: Metadata = serialiseCIP30(organiserAccount, categoryMetadataMap, "CATEGORY_REGISTRATION")
var categoryTxData: Array[Byte] = serialiseTx(organiserAccount, categoryMetadata)
var categoryTxId: String = submitTx(categoryTxData)
println("Category txId:" + categoryTxId)
return categoryTxId != null
}
return false
}
def submitTx(txData: Array[Byte]): String = {
var result = backendService.getTransactionService().submitTransaction(txData)
var res = if (result.isSuccessful()) {
result.getValue()
} else {
println(s"Tx failure, reason: ${result.getResponse()}")
null
}
println("Sleeping for 1 sec.")
Thread.sleep(1000L)
res
}
def serialiseTx(organiserAccount: Account, metadata: Metadata) = {
val quickTxBuilder = new QuickTxBuilder(backendService)
val tx = new Tx()
.payToAddress(organiserAccount.baseAddress(), Amount.ada(2.0))
.attachMetadata(metadata)
.from(organiserAccount.baseAddress())
quickTxBuilder.compose(tx)
.withSigner(SignerProviders.signerFrom(organiserAccount))
.buildAndSign()
.serialize()
}
def serialiseCIP30(organiserAccount: Account, childMetadata: MetadataMap, onChainEventType: String): Metadata = {
val stakeAddress = organiserAccount.stakeAddress()
println("stakeAddress:" + stakeAddress)
val stakeAddressAccount = new Address(stakeAddress)
val data = CborSerializationUtil.serialize(childMetadata.getMap())
val hashedData = blake2bHash224(data)
val dataSignature = CIP30DataSigner.INSTANCE.signData(
stakeAddressAccount.getBytes(),
hashedData,
organiserAccount.stakeHdKeyPair().getPrivateKey().getKeyData(),
organiserAccount.stakeHdKeyPair().getPublicKey().getKeyData()
);
val envelope = MetadataBuilder.createMap()
envelope.put("type", onChainEventType)
envelope.put("signature", dataSignature.signature())
envelope.put("key", dataSignature.key())
envelope.put("signatureType", "HASH_ONLY")
envelope.put("hashType", "BLAKE2B_224")
envelope.put("payload", childMetadata)
envelope.put("format", "CIP-30")
envelope.put("subFormat", "CBOR")
val metadata = MetadataBuilder.createMetadata()
metadata.put(metadataLabel, envelope)
metadata
}
def createEvent(createEventCommand: CreateEventCommand, slot: Long): MetadataMap = {
var map = MetadataBuilder.createMap()
map.put("type", "EVENT_REGISTRATION")
map.put("name", createEventCommand.id)
map.put("team", createEventCommand.team)
map.put("votingEventType", createEventCommand.votingEventType)
map.put("schemaVersion", createEventCommand.schemaVersion)
map.put("creationSlot", BigInteger.valueOf(slot))
if (List("STAKE_BASED", "BALANCE_BASED").contains(createEventCommand.votingEventType)) {
map.put("startEpoch", BigInteger.valueOf(createEventCommand.startEpoch.get))
map.put("endEpoch", BigInteger.valueOf(createEventCommand.endEpoch.get))
map.put("snapshotEpoch", BigInteger.valueOf(createEventCommand.snapshotEpoch.get))
map.put("votingPowerAsset", createEventCommand.votingPowerAsset.get)
}
if (createEventCommand.votingEventType.equals("USER_BASED")) {
map.put("startSlot", BigInteger.valueOf(createEventCommand.startSlot.get))
map.put("endSlot", BigInteger.valueOf(createEventCommand.endSlot.get))
}
map.put("options", createEventOptions(createEventCommand))
map
}
def createEventOptions(createEventCommand: CreateEventCommand): MetadataMap = {
val optionsMap = MetadataBuilder.createMap()
optionsMap.put("allowVoteChanging", toBigInteger(createEventCommand.allowVoteChanging))
optionsMap.put("categoryResultsWhileVoting", toBigInteger(createEventCommand.categoryResultsWhileVoting))
optionsMap.put("highLevelResultsWhileVoting", toBigInteger(createEventCommand.highLevelResultsWhileVoting))
optionsMap
}
def createCategory(createCategoryCommand: CreateCategoryCommand, slot: Long): MetadataMap = {
var map = MetadataBuilder.createMap()
map.put("type", "CATEGORY_REGISTRATION")
map.put("name", createCategoryCommand.id)
map.put("event", createCategoryCommand.event)
map.put("schemaVersion", createCategoryCommand.schemaVersion)
map.put("creationSlot", BigInteger.valueOf(slot))
map.put("options", createCategoryOptions(createCategoryCommand))
map.put("proposals", createProposals(createCategoryCommand))
map
}
def createProposals(createCategoryCommand: CreateCategoryCommand): MetadataList = {
val proposalsList = MetadataBuilder.createList()
for (proposal <- createCategoryCommand.proposals) {
val proposalMap = MetadataBuilder.createMap()
if (createCategoryCommand.gdprProtection) {
proposalMap.put("id", proposal.id);
} else {
proposalMap.put("id", proposal.id);
proposalMap.put("name", proposal.name);
}
proposalsList.add(proposalMap);
}
proposalsList
}
def createCategoryOptions(createCategoryCommand: CreateCategoryCommand): MetadataMap = {
var optionsMap = MetadataBuilder.createMap()
optionsMap.put("gdprProtection", toBigInteger(createCategoryCommand.gdprProtection));
optionsMap
}
@main
def main() = {
val isOrganiserAccountLoaded = topUpAccount(organiserAccount, 10000)
if (isOrganiserAccountLoaded) {
//val isRegistered = true
val isRegistered = registerEventAndCategories(organiserAccount, mapper, eventName)
if (isRegistered) {
println("Onchain event and categories registration success!")
println("sleep for 5 seconds")
Thread.sleep(5 * 1000L) // lets wait until voting-app and verification app ingest event and category data
println("woke up...")
for (i <- 1 to 10000) {
try {
val account = Account(Networks.testnet())
val isPreloaded = topUpAccount(account, amountAda)
if (isPreloaded) {
castVote(account, amountAda)
}
} catch {
case e: Exception => println(s"Couldn't cast vote.")
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment