Skip to content

Commit

Permalink
Merge branch 'master' of github.com:AcalaNetwork/boka
Browse files Browse the repository at this point in the history
* 'master' of github.com:AcalaNetwork/boka:
  implements accumulation  (#111)
  pvm memory optimization (#109)
  • Loading branch information
MacOMNI committed Sep 11, 2024
2 parents b3af474 + d81c607 commit df0a630
Show file tree
Hide file tree
Showing 17 changed files with 581 additions and 202 deletions.
73 changes: 73 additions & 0 deletions Blockchain/Sources/Blockchain/AccumulateFunction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Foundation
import Utils

public struct AccumulateArguments {
public var result: WorkResult
public var paylaodHash: Data32
public var packageHash: Data32
public var authorizationOutput: Data

public init(result: WorkResult, paylaodHash: Data32, packageHash: Data32, authorizationOutput: Data) {
self.result = result
self.paylaodHash = paylaodHash
self.packageHash = packageHash
self.authorizationOutput = authorizationOutput
}
}

public struct DeferredTransfers {
// s
public var sender: ServiceIndex
// d
public var destination: ServiceIndex
// a
public var amount: Balance
// m
public var memo: Data64
// g
public var gasLimit: Gas

public init(sender: ServiceIndex, destination: ServiceIndex, amount: Balance, memo: Data64, gasLimit: Gas) {
self.sender = sender
self.destination = destination
self.amount = amount
self.memo = memo
self.gasLimit = gasLimit
}
}

public struct AccumlateResultContext {
// s: updated current account
public var account: ServiceAccount?
// c
public var authorizationQueue: ConfigFixedSizeArray<
ConfigFixedSizeArray<
Data32,
ProtocolConfig.MaxAuthorizationsQueueItems
>,
ProtocolConfig.TotalNumberOfCores
>
// v
public var validatorQueue: ConfigFixedSizeArray<
ValidatorKey, ProtocolConfig.TotalNumberOfValidators
>
// i
public var serviceIndex: ServiceIndex
// t
public var transfers: [DeferredTransfers]
// n
public var newAccounts: [ServiceIndex: ServiceAccount]
// p
public var privilegedServices: PrivilegedServices
}

public protocol AccumulateFunction {
func invoke(
config: ProtocolConfigRef,
service: ServiceIndex,
code: Data,
serviceAccounts: [ServiceIndex: ServiceAccount],
gas: Gas,
arguments: [AccumulateArguments]
) throws -> (ctx: AccumlateResultContext, result: Data32?)
}
161 changes: 161 additions & 0 deletions Blockchain/Sources/Blockchain/Accumulation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import Utils

public enum AccumulationError: Error {
case invalidServiceIndex
case duplicatedServiceIndex
}

public struct AccumulationOutput {
public var commitments: [(ServiceIndex, Data32)]
public var privilegedServices: PrivilegedServices
public var validatorQueue: ConfigFixedSizeArray<
ValidatorKey, ProtocolConfig.TotalNumberOfValidators
>
public var authorizationQueue: ConfigFixedSizeArray<
ConfigFixedSizeArray<
Data32,
ProtocolConfig.MaxAuthorizationsQueueItems
>,
ProtocolConfig.TotalNumberOfCores
>
public var serviceAccounts: [ServiceIndex: ServiceAccount]
}

public protocol Accumulation {
var privilegedServices: PrivilegedServices { get }
var serviceAccounts: [ServiceIndex: ServiceAccount] { get }
var accumlateFunction: AccumulateFunction { get }
var onTransferFunction: OnTransferFunction { get }
}

extension Accumulation {
public func update(config: ProtocolConfigRef, workReports: [WorkReport]) throws -> AccumulationOutput {
var servicesGasRatio: [ServiceIndex: Gas] = [:]
var servicesGas: [ServiceIndex: Gas] = [:]

// privileged gas
for (service, gas) in privilegedServices.basicGas {
servicesGas[service] = gas
}

let totalGasRatio = workReports.flatMap(\.results).reduce(0) { $0 + $1.gasRatio }
let totalMinimalGas = try workReports.flatMap(\.results)
.reduce(0) { try $0 + serviceAccounts[$1.serviceIndex].unwrap(orError: AccumulationError.invalidServiceIndex).minAccumlateGas }
for report in workReports {
for result in report.results {
servicesGasRatio[result.serviceIndex, default: 0] += result.gasRatio
servicesGas[result.serviceIndex, default: 0] += try serviceAccounts[result.serviceIndex]
.unwrap(orError: AccumulationError.invalidServiceIndex).minAccumlateGas
}
}
let remainingGas = config.value.coreAccumulationGas - totalMinimalGas

for (service, gas) in servicesGas {
servicesGas[service] = gas + servicesGasRatio[service, default: 0] * remainingGas / totalGasRatio
}

var serviceArguments: [ServiceIndex: [AccumulateArguments]] = [:]

// ensure privileged services will be called
for service in privilegedServices.basicGas.keys {
serviceArguments[service] = []
}

for report in workReports {
for result in report.results {
serviceArguments[result.serviceIndex, default: []].append(AccumulateArguments(
result: result,
paylaodHash: result.payloadHash,
packageHash: report.packageSpecification.workPackageHash,
authorizationOutput: report.authorizationOutput
))
}
}

var commitments = [(ServiceIndex, Data32)]()
var newPrivilegedServices: PrivilegedServices?
var newValidatorQueue: ConfigFixedSizeArray<
ValidatorKey, ProtocolConfig.TotalNumberOfValidators
>?
var newAuthorizationQueue: ConfigFixedSizeArray<
ConfigFixedSizeArray<
Data32,
ProtocolConfig.MaxAuthorizationsQueueItems
>,
ProtocolConfig.TotalNumberOfCores
>?

var newServiceAccounts = serviceAccounts

var transferReceivers = [ServiceIndex: [DeferredTransfers]]()

for (service, arguments) in serviceArguments {
guard let gas = servicesGas[service] else {
assertionFailure("unreachable: service not found")
throw AccumulationError.invalidServiceIndex
}
let acc = try serviceAccounts[service].unwrap(orError: AccumulationError.invalidServiceIndex)
guard let code = acc.preimages[acc.codeHash] else {
continue
}
let (ctx, commitment) = try accumlateFunction.invoke(
config: config,
service: service,
code: code,
serviceAccounts: serviceAccounts,
gas: gas,
arguments: arguments
)
if let commitment {
commitments.append((service, commitment))
}

for (service, account) in ctx.newAccounts {
guard newServiceAccounts[service] == nil else {
throw AccumulationError.duplicatedServiceIndex
}
newServiceAccounts[service] = account
}

newServiceAccounts[service] = ctx.account

switch service {
case privilegedServices.empower:
newPrivilegedServices = ctx.privilegedServices
case privilegedServices.assign:
newAuthorizationQueue = ctx.authorizationQueue
case privilegedServices.designate:
newValidatorQueue = ctx.validatorQueue
default:
break
}

for transfer in ctx.transfers {
transferReceivers[transfer.sender, default: []].append(transfer)
}
}

for (service, transfers) in transferReceivers {
let acc = try serviceAccounts[service].unwrap(orError: AccumulationError.invalidServiceIndex)
guard let code = acc.preimages[acc.codeHash] else {
continue
}
newServiceAccounts[service] = try onTransferFunction.invoke(
config: config,
service: service,
code: code,
serviceAccounts: newServiceAccounts,
transfers: transfers
)
}

return .init(
commitments: commitments,
// those cannot be nil because priviledge services are always called
privilegedServices: newPrivilegedServices!,
validatorQueue: newValidatorQueue!,
authorizationQueue: newAuthorizationQueue!,
serviceAccounts: newServiceAccounts
)
}
}
27 changes: 15 additions & 12 deletions Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ public struct ProtocolConfig: Sendable {
public var auditBiasFactor: Int

// GA: The total gas allocated to a core for Accumulation.
public var coreAccumulationGas: Int
public var coreAccumulationGas: Gas

// GI: The gas allocated to invoke a work-package’s Is-Authorized logic.
public var workPackageAuthorizerGas: Int
public var workPackageAuthorizerGas: Gas

// GR: The total gas allocated for a work-package’s Refine logic.
public var workPackageRefineGas: Int
public var workPackageRefineGas: Gas

// H = 8: The size of recent history, in blocks.
public var recentHistorySize: Int
Expand Down Expand Up @@ -116,9 +116,9 @@ public struct ProtocolConfig: Sendable {
preimagePurgePeriod: Int,
epochLength: Int,
auditBiasFactor: Int,
coreAccumulationGas: Int,
workPackageAuthorizerGas: Int,
workPackageRefineGas: Int,
coreAccumulationGas: Gas,
workPackageAuthorizerGas: Gas,
workPackageRefineGas: Gas,
recentHistorySize: Int,
maxWorkItems: Int,
maxTicketsPerExtrinsic: Int,
Expand Down Expand Up @@ -248,23 +248,26 @@ extension ProtocolConfig {
}
}

public enum CoreAccumulationGas: ReadInt {
public enum CoreAccumulationGas: ReadUInt64 {
public typealias TConfig = ProtocolConfigRef
public static func read(config: ProtocolConfigRef) -> Int {
public typealias TOutput = Gas
public static func read(config: ProtocolConfigRef) -> Gas {
config.value.coreAccumulationGas
}
}

public enum WorkPackageAuthorizerGas: ReadInt {
public enum WorkPackageAuthorizerGas: ReadUInt64 {
public typealias TConfig = ProtocolConfigRef
public static func read(config: ProtocolConfigRef) -> Int {
public typealias TOutput = Gas
public static func read(config: ProtocolConfigRef) -> Gas {
config.value.workPackageAuthorizerGas
}
}

public enum WorkPackageRefineGas: ReadInt {
public enum WorkPackageRefineGas: ReadUInt64 {
public typealias TConfig = ProtocolConfigRef
public static func read(config: ProtocolConfigRef) -> Int {
public typealias TOutput = Gas
public static func read(config: ProtocolConfigRef) -> Gas {
config.value.workPackageRefineGas
}
}
Expand Down
11 changes: 11 additions & 0 deletions Blockchain/Sources/Blockchain/OnTransferFunction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public protocol OnTransferFunction {
func invoke(
config: ProtocolConfigRef,
service: ServiceIndex,
code: Data,
serviceAccounts: [ServiceIndex: ServiceAccount],
transfers: [DeferredTransfers]
) throws -> ServiceAccount
}
45 changes: 42 additions & 3 deletions Blockchain/Sources/Blockchain/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public final class Runtime {
case invalidAssuranceParentHash
case invalidAssuranceSignature
case assuranceForEmptyCore
case preimagesNotSorted
case invalidPreimageServiceIndex
case duplicatedPreimage
case other(any Swift.Error)
case validateError(any Swift.Error)
}
Expand Down Expand Up @@ -102,7 +105,12 @@ public final class Runtime {
try updateDisputes(block: block, state: &newState)

// depends on Safrole and Disputes
try updateReports(block: block, state: &newState)
let availableReports = try updateReports(block: block, state: &newState)
let res = try newState.update(config: config, workReports: availableReports)
newState.privilegedServices = res.privilegedServices
newState.serviceAccounts = res.serviceAccounts
newState.authorizationQueue = res.authorizationQueue
newState.validatorQueue = res.validatorQueue

newState.coreAuthorizationPool = try updateAuthorizationPool(
block: block, state: prevState
Expand Down Expand Up @@ -202,7 +210,8 @@ public final class Runtime {
return pool
}

public func updateReports(block: BlockRef, state newState: inout State) throws {
// returns available reports
public func updateReports(block: BlockRef, state newState: inout State) throws -> [WorkReport] {
for assurance in block.extrinsic.availability.assurances {
let hash = Blake2b256.hash(assurance.parentHash, assurance.assurance)
let payload = SigningContext.available + hash.data
Expand All @@ -220,16 +229,46 @@ public final class Runtime {
}
}

var availableReports = [WorkReport]()

for (idx, count) in availabilityCount.enumerated() where count > 0 {
guard newState.reports[idx] != nil else {
guard let report = newState.reports[idx] else {
throw Error.assuranceForEmptyCore
}
if count >= ProtocolConfig.TwoThirdValidatorsPlusOne.read(config: config) {
availableReports.append(report.workReport)
newState.reports[idx] = nil // remove available report from pending reports
}
}

newState.reports = try newState.update(config: config, extrinsic: block.extrinsic.reports)

return availableReports
}

public func updatePreimages(block: BlockRef, state newState: inout State) throws {
let preimages = block.extrinsic.preimages.preimages

guard preimages.isSortedAndUnique() else {
throw Error.preimagesNotSorted
}

for preimage in preimages {
guard var acc = newState.serviceAccounts[preimage.serviceIndex] else {
throw Error.invalidPreimageServiceIndex
}

let hash = preimage.data.blake2b256hash()
let hashAndLength = HashAndLength(hash: hash, length: UInt32(preimage.data.count))
guard acc.preimages[hash] == nil, acc.preimageInfos[hashAndLength] == nil else {
throw Error.duplicatedPreimage
}

acc.preimages[hash] = preimage.data
acc.preimageInfos[hashAndLength] = .init([newState.timeslot])

newState.serviceAccounts[preimage.serviceIndex] = acc
}
}

// TODO: add tests
Expand Down
Loading

0 comments on commit df0a630

Please sign in to comment.