feat: create a circuit module

This commit is contained in:
NicoSerranoP
2024-05-20 17:05:19 -05:00
parent dbb8c8f8ea
commit d57ed0bbf2
16 changed files with 185 additions and 108 deletions

View File

@@ -7,6 +7,7 @@ import { JwtModule } from "@nestjs/jwt"
import { CeremoniesModule } from "./ceremonies/ceremonies.module"
import { StorageModule } from "./storage/storage.module"
import { ScheduleModule } from "@nestjs/schedule"
import { CircuitsModule } from "./circuits/circuits.module"
@Module({
imports: [
@@ -28,7 +29,8 @@ import { ScheduleModule } from "@nestjs/schedule"
AuthModule,
UsersModule,
CeremoniesModule,
StorageModule
StorageModule,
CircuitsModule
]
})
export class AppModule {}

View File

@@ -6,13 +6,14 @@ import { UserEntity } from "src/users/entities/user.entity"
import { UsersService } from "src/users/service/users.service"
import { CeremoniesService } from "src/ceremonies/service/ceremonies.service"
import { CeremonyEntity } from "src/ceremonies/entities/ceremony.entity"
import { CircuitEntity } from "src/ceremonies/entities/circuit.entity"
import { ParticipantEntity } from "src/ceremonies/entities/participant.entity"
import { CircuitsService } from "src/circuits/service/circuits.service"
import { CircuitEntity } from "src/circuits/entities/circuit.entity"
@Module({
imports: [SequelizeModule.forFeature([UserEntity, CeremonyEntity, CircuitEntity, ParticipantEntity])],
exports: [SequelizeModule],
controllers: [AuthController],
providers: [AuthService, UsersService, CeremoniesService]
providers: [AuthService, UsersService, CeremoniesService, CircuitsService]
})
export class AuthModule {}

View File

@@ -3,14 +3,15 @@ import { CeremoniesController } from "./controller/ceremonies.controller"
import { CeremoniesService } from "./service/ceremonies.service"
import { SequelizeModule } from "@nestjs/sequelize"
import { CeremonyEntity } from "./entities/ceremony.entity"
import { CircuitEntity } from "./entities/circuit.entity"
import { UsersService } from "src/users/service/users.service"
import { UserEntity } from "src/users/entities/user.entity"
import { ParticipantEntity } from "./entities/participant.entity"
import { CircuitsService } from "src/circuits/service/circuits.service"
import { CircuitEntity } from "src/circuits/entities/circuit.entity"
@Module({
controllers: [CeremoniesController],
providers: [CeremoniesService, UsersService],
providers: [CeremoniesService, CircuitsService, UsersService],
imports: [SequelizeModule.forFeature([CeremonyEntity, CircuitEntity, ParticipantEntity, UserEntity])],
exports: [SequelizeModule]
})

View File

@@ -1,11 +1,11 @@
import { CeremonyState, CeremonyTimeoutType, CeremonyType } from "@p0tion/actions"
import { ArrayMinSize, IsArray, IsEnum, IsIn, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator"
import { CircuitDto } from "./circuit-dto"
import { Type } from "class-transformer"
import { AuthProvider } from "src/types/enums"
import { GithubDto } from "./github-dto"
import { SiweDto } from "./siwe-dto"
import { BandadaDto } from "./bandada-dto"
import { CircuitDto } from "src/circuits/dto/circuits-dto"
export class CeremonyDto {
@IsString()

View File

@@ -1,12 +1,12 @@
import { AutoIncrement, Column, DataType, ForeignKey, HasMany, Model, Table } from "sequelize-typescript"
import { CeremonyState, CeremonyTimeoutType, CeremonyType } from "@p0tion/actions"
import { CircuitEntity } from "./circuit.entity"
import { AuthProvider } from "src/types/enums"
import { GithubDto } from "../dto/github-dto"
import { SiweDto } from "../dto/siwe-dto"
import { BandadaDto } from "../dto/bandada-dto"
import { UserEntity } from "src/users/entities/user.entity"
import { ParticipantEntity } from "./participant.entity"
import { CircuitEntity } from "src/circuits/entities/circuit.entity"
@Table
export class CeremonyEntity extends Model {

View File

@@ -2,32 +2,26 @@ import { Injectable } from "@nestjs/common"
import { CeremonyDto } from "../dto/ceremony-dto"
import { InjectModel } from "@nestjs/sequelize"
import { CeremonyEntity } from "../entities/ceremony.entity"
import { CircuitEntity } from "../entities/circuit.entity"
import { CircuitDto } from "../dto/circuit-dto"
import {
CeremonyState,
CircuitContributionVerificationMechanism,
computeDiskSizeForVM,
createEC2Instance,
getBucketName,
terminateEC2Instance,
vmBootstrapCommand,
vmBootstrapScriptFilename,
vmDependenciesAndCacheArtifactsCommand
terminateEC2Instance
} from "@p0tion/actions"
import { createEC2Client, getAWSVariables, getFinalContribution, uploadFileToBucketNoFile } from "src/lib/utils"
import { createEC2Client, getFinalContribution } from "src/lib/utils"
import { SPECIFIC_ERRORS, logAndThrowError, printLog } from "src/lib/errors"
import { LogLevel } from "src/types/enums"
import { Cron, CronExpression } from "@nestjs/schedule"
import { Op } from "sequelize"
import { CircuitsService } from "src/circuits/service/circuits.service"
import { CircuitEntity } from "src/circuits/entities/circuit.entity"
@Injectable()
export class CeremoniesService {
constructor(
@InjectModel(CeremonyEntity)
private ceremonyModel: typeof CeremonyEntity,
@InjectModel(CircuitEntity)
private circuitModel: typeof CircuitEntity
private circuitsService: CircuitsService
) {}
async create(ceremonyDto: CeremonyDto) {
@@ -35,95 +29,13 @@ export class CeremoniesService {
const ceremony = await this.ceremonyModel.create(ceremonyData as any)
const circuitEntities = await this.createCircuits(circuits, ceremony)
const circuitEntities = await this.circuitsService.createCircuits(circuits, ceremony)
await ceremony.$set("circuits", circuitEntities)
printLog(`Setup completed for ceremony ${ceremony.id}`, LogLevel.DEBUG)
return ceremony
}
async createCircuits(circuits: CircuitDto[], ceremony: CeremonyEntity) {
const bucketName = getBucketName(ceremony.prefix, String(process.env.AWS_CEREMONY_BUCKET_POSTFIX))
const ceremonyId = ceremony.id
const circuitEntities = []
for (let i = 0, ni = circuits.length; i < ni; i++) {
let circuit = circuits[i]
// create the waiting queue object
circuit = {
...circuit,
waitingQueue: {
contributors: [],
currentContributor: "",
completedContributions: 0,
failedContributions: 0
}
}
// create VMs outside this server if the option was selected
if (circuit.verification.cfOrVm === CircuitContributionVerificationMechanism.VM) {
const { instance, vmDiskSize } = await this.setupAWSEnvironment(circuit, bucketName)
// Update the circuit document info accordingly.
circuit = {
...circuit,
verification: {
cfOrVm: circuit.verification.cfOrVm,
vm: {
vmConfigurationType: circuit.verification.vm.vmConfigurationType,
vmInstanceId: instance.instanceId,
vmDiskSize
}
}
}
}
const circuitEntity = await this.circuitModel.create({ ...circuit, ceremonyId })
circuitEntities.push(circuitEntity)
}
return circuitEntities
}
async setupAWSEnvironment(circuit: CircuitDto, bucketName: string) {
// VM command to be run at the startup.
const startupCommand = vmBootstrapCommand(`${bucketName}/circuits/${circuit.name!}`)
// Get EC2 client.
const ec2Client = await createEC2Client()
// Get AWS variables.
const { snsTopic, region } = getAWSVariables()
// Prepare dependencies and cache artifacts command.
const vmCommands = vmDependenciesAndCacheArtifactsCommand(
`${bucketName}/${circuit.files.initialZkeyStoragePath}`,
`${bucketName}/${circuit.files.potStoragePath}`,
snsTopic,
region
)
printLog(`Check VM dependencies and cache artifacts commands ${vmCommands.join("\n")}`, LogLevel.DEBUG)
// Upload the post-startup commands script file.
printLog(`Uploading VM post-startup commands script file ${vmBootstrapScriptFilename}`, LogLevel.DEBUG)
await uploadFileToBucketNoFile(
bucketName,
`circuits/${circuit.name!}/${vmBootstrapScriptFilename}`,
vmCommands.join("\n")
)
// TODO: should we create a AWS instance or run it in a docker file?
// Compute the VM disk space requirement (in GB).
const vmDiskSize = computeDiskSizeForVM(circuit.zKeySizeInBytes, circuit.metadata.pot)
printLog(`Check VM startup commands ${startupCommand.join("\n")}`, LogLevel.DEBUG)
// Configure and instantiate a new VM based on the coordinator input.
const instance = await createEC2Instance(
ec2Client,
startupCommand,
circuit.verification.vm.vmConfigurationType,
vmDiskSize,
circuit.verification.vm.vmDiskType
)
return { instance, vmDiskSize }
}
findById(id: number) {
return this.ceremonyModel.findByPk(id, { include: [CircuitEntity] })
}

View File

@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common"
import { CircuitsController } from "./controller/circuits.controller"
import { CircuitsService } from "./service/circuits.service"
import { SequelizeModule } from "@nestjs/sequelize"
import { CircuitEntity } from "./entities/circuit.entity"
@Module({
controllers: [CircuitsController],
providers: [CircuitsService],
imports: [SequelizeModule.forFeature([CircuitEntity])]
})
export class CircuitsModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from "@nestjs/testing"
import { CircuitsController } from "./circuits.controller"
describe("CircuitsController", () => {
let controller: CircuitsController
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CircuitsController]
}).compile()
controller = module.get<CircuitsController>(CircuitsController)
})
it("should be defined", () => {
expect(controller).toBeDefined()
})
})

View File

@@ -0,0 +1,4 @@
import { Controller } from "@nestjs/common"
@Controller("circuits")
export class CircuitsController {}

View File

@@ -1,6 +1,6 @@
import { CircuitDto } from "./circuit-dto"
import { CircuitDto } from "./circuits-dto"
describe("CircuitDto", () => {
describe("CircuitsDto", () => {
it("should be defined", () => {
expect(new CircuitDto()).toBeDefined()
})

View File

@@ -1,5 +1,4 @@
import { AutoIncrement, Column, DataType, ForeignKey, Model, Table } from "sequelize-typescript"
import { CeremonyEntity } from "./ceremony.entity"
import {
AvgTimingsDto,
CompilationArtifactsDto,
@@ -9,7 +8,8 @@ import {
TemplateDto,
VerificationDto,
WaitingQueueDto
} from "../dto/circuit-dto"
} from "../dto/circuits-dto"
import { CeremonyEntity } from "src/ceremonies/entities/ceremony.entity"
@Table
export class CircuitEntity extends Model {

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from "@nestjs/testing"
import { CircuitsService } from "./circuits.service"
describe("CircuitsService", () => {
let service: CircuitsService
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CircuitsService]
}).compile()
service = module.get<CircuitsService>(CircuitsService)
})
it("should be defined", () => {
expect(service).toBeDefined()
})
})

View File

@@ -0,0 +1,108 @@
import { Injectable } from "@nestjs/common"
import { InjectModel } from "@nestjs/sequelize"
import { CircuitEntity } from "../entities/circuit.entity"
import {
CircuitContributionVerificationMechanism,
computeDiskSizeForVM,
createEC2Client,
createEC2Instance,
getBucketName,
vmBootstrapCommand,
vmBootstrapScriptFilename,
vmDependenciesAndCacheArtifactsCommand
} from "@p0tion/actions"
import { CircuitDto } from "../dto/circuits-dto"
import { CeremonyEntity } from "src/ceremonies/entities/ceremony.entity"
import { getAWSVariables, uploadFileToBucketNoFile } from "src/lib/utils"
import { printLog } from "src/lib/errors"
import { LogLevel } from "src/types/enums"
@Injectable()
export class CircuitsService {
constructor(
@InjectModel(CircuitEntity)
private circuitModel: typeof CircuitEntity
) {}
async createCircuits(circuits: CircuitDto[], ceremony: CeremonyEntity) {
const bucketName = getBucketName(ceremony.prefix, String(process.env.AWS_CEREMONY_BUCKET_POSTFIX))
const ceremonyId = ceremony.id
const circuitEntities = []
for (let i = 0, ni = circuits.length; i < ni; i++) {
let circuit = circuits[i]
// create the waiting queue object
circuit = {
...circuit,
waitingQueue: {
contributors: [],
currentContributor: "",
completedContributions: 0,
failedContributions: 0
}
}
// create VMs outside this server if the option was selected
if (circuit.verification.cfOrVm === CircuitContributionVerificationMechanism.VM) {
const { instance, vmDiskSize } = await this.setupAWSEnvironment(circuit, bucketName)
// Update the circuit document info accordingly.
circuit = {
...circuit,
verification: {
cfOrVm: circuit.verification.cfOrVm,
vm: {
vmConfigurationType: circuit.verification.vm.vmConfigurationType,
vmInstanceId: instance.instanceId,
vmDiskSize
}
}
}
}
const circuitEntity = await this.circuitModel.create({ ...circuit, ceremonyId })
circuitEntities.push(circuitEntity)
}
return circuitEntities
}
async setupAWSEnvironment(circuit: CircuitDto, bucketName: string) {
// VM command to be run at the startup.
const startupCommand = vmBootstrapCommand(`${bucketName}/circuits/${circuit.name!}`)
// Get EC2 client.
const ec2Client = await createEC2Client()
// Get AWS variables.
const { snsTopic, region } = getAWSVariables()
// Prepare dependencies and cache artifacts command.
const vmCommands = vmDependenciesAndCacheArtifactsCommand(
`${bucketName}/${circuit.files.initialZkeyStoragePath}`,
`${bucketName}/${circuit.files.potStoragePath}`,
snsTopic,
region
)
printLog(`Check VM dependencies and cache artifacts commands ${vmCommands.join("\n")}`, LogLevel.DEBUG)
// Upload the post-startup commands script file.
printLog(`Uploading VM post-startup commands script file ${vmBootstrapScriptFilename}`, LogLevel.DEBUG)
await uploadFileToBucketNoFile(
bucketName,
`circuits/${circuit.name!}/${vmBootstrapScriptFilename}`,
vmCommands.join("\n")
)
// TODO: should we create a AWS instance or run it in a docker file?
// Compute the VM disk space requirement (in GB).
const vmDiskSize = computeDiskSizeForVM(circuit.zKeySizeInBytes, circuit.metadata.pot)
printLog(`Check VM startup commands ${startupCommand.join("\n")}`, LogLevel.DEBUG)
// Configure and instantiate a new VM based on the coordinator input.
const instance = await createEC2Instance(
ec2Client,
startupCommand,
circuit.verification.vm.vmConfigurationType,
vmDiskSize,
circuit.verification.vm.vmDiskType
)
return { instance, vmDiskSize }
}
}

View File

@@ -18,10 +18,10 @@ import {
getBucketName,
getZkeyStorageFilePath
} from "@p0tion/actions"
import { CircuitEntity } from "src/ceremonies/entities/circuit.entity"
import { ParticipantEntity } from "src/ceremonies/entities/participant.entity"
import { CeremoniesService } from "src/ceremonies/service/ceremonies.service"
import { ParticipantsService } from "src/ceremonies/service/participants.service"
import { CircuitEntity } from "src/circuits/entities/circuit.entity"
import { COMMON_ERRORS, SPECIFIC_ERRORS, logAndThrowError, makeError, printLog } from "src/lib/errors"
import { getS3Client } from "src/lib/services"
import { getCurrentServerTimestampInMillis } from "src/lib/utils"

View File

@@ -6,13 +6,14 @@ import { UserEntity } from "src/users/entities/user.entity"
import { SequelizeModule } from "@nestjs/sequelize"
import { CeremoniesService } from "src/ceremonies/service/ceremonies.service"
import { CeremonyEntity } from "src/ceremonies/entities/ceremony.entity"
import { CircuitEntity } from "src/ceremonies/entities/circuit.entity"
import { ParticipantEntity } from "src/ceremonies/entities/participant.entity"
import { ParticipantsService } from "src/ceremonies/service/participants.service"
import { CircuitsService } from "src/circuits/service/circuits.service"
import { CircuitEntity } from "src/circuits/entities/circuit.entity"
@Module({
controllers: [StorageController],
imports: [SequelizeModule.forFeature([UserEntity, CeremonyEntity, CircuitEntity, ParticipantEntity])],
providers: [StorageService, UsersService, CeremoniesService, ParticipantsService]
providers: [StorageService, UsersService, CeremoniesService, CircuitsService, ParticipantsService]
})
export class StorageModule {}