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

add log host call #264

Merged
merged 1 commit into from
Jan 8, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import Codec
import Foundation
import PolkaVM
import TracingUtils
import Utils

private let logger = Logger(label: "HostCalls")

// MARK: - General

/// Get gas remaining
Expand Down Expand Up @@ -977,3 +980,105 @@
context.pvms[reg7] = nil
}
}

/// A host call for passing a debugging message from the service/authorizer to the hosting environment for logging to the node operator.
public class Log: HostCall {
public static var identifier: UInt8 { 100 }

Check warning on line 986 in Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift#L986

Added line #L986 was not covered by tests

public enum Level: UInt32, Codable {
case error = 0
case debug = 1
case info = 2
case warn = 3
case trace = 4

var description: String {
switch self {
case .error: "ERROR"
case .debug: "DEBUG"
case .info: "INFO"
case .warn: "WARN"
case .trace: "TRACE"
}
}
}

public struct Details: Codable {
public let time: String
public let level: Level
public let target: Data?
public let message: Data
public let core: CoreIndex?
public let service: ServiceIndex?

public var json: JSON {
JSON.dictionary([
"time": .string(time),
"level": .string(level.description),
"message": .string(String(data: message, encoding: .utf8) ?? "invalid string"),
"target": target != nil ? .string(String(data: target!, encoding: .utf8) ?? "invalid string") : .null,
"service": service != nil ? .string(String(service!)) : .null,
"core": core != nil ? .string(String(core!)) : .null,
])
}

public var str: String {
var result = time + " \(level.description)"
if let core {
result += " @\(core)"
}

Check warning on line 1029 in Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift#L1028-L1029

Added lines #L1028 - L1029 were not covered by tests
if let service {
result += " #\(service)"
}

Check warning on line 1032 in Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift#L1031-L1032

Added lines #L1031 - L1032 were not covered by tests
if let target {
result += " \(String(data: target, encoding: .utf8) ?? "invalid string")"
}
result += " \(String(data: message, encoding: .utf8) ?? "invalid string")"

return result
}
}

public var core: CoreIndex?
public var service: ServiceIndex?

public init(core: CoreIndex? = nil, service: ServiceIndex? = nil) {
self.core = core
self.service = service
}

Check warning on line 1048 in Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift#L1045-L1048

Added lines #L1045 - L1048 were not covered by tests

public func _callImpl(config _: ProtocolConfigRef, state: VMState) async throws {
let regs: [UInt32] = state.readRegisters(in: 7 ..< 12)
let level = regs[0]
let target = regs[1] == 0 && regs[2] == 0 ? nil : try state.readMemory(address: regs[1], length: Int(regs[2]))
let message = try state.readMemory(address: regs[3], length: Int(regs[4]))

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH-mm-ss"
let time = dateFormatter.string(from: Date())

let details = Details(
time: time,
level: Level(rawValue: level) ?? .debug,
target: target,
message: message,
core: core,
service: service
)

switch level {
case 0:
logger.error(Logger.Message(stringLiteral: details.str))
case 1:
logger.warning(Logger.Message(stringLiteral: details.str))
case 2:
logger.info(Logger.Message(stringLiteral: details.str))
case 3:
logger.debug(Logger.Message(stringLiteral: details.str))
case 4:
logger.trace(Logger.Message(stringLiteral: details.str))
default:
logger.error("Invalid log level: \(level)")
}
}

Check warning on line 1083 in Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift#L1050-L1083

Added lines #L1050 - L1083 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
return await Solicit(x: &context.x, timeslot: timeslot).call(config: config, state: state)
case Forget.identifier:
return await Forget(x: &context.x, timeslot: timeslot).call(config: config, state: state)
case Log.identifier:
return await Log(service: context.x.serviceIndex).call(config: config, state: state)

Check warning on line 65 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/AccumulateContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/AccumulateContext.swift#L64-L65

Added lines #L64 - L65 were not covered by tests
default:
state.consumeGas(Gas(10))
state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.WHAT.rawValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
}

public func dispatch(index: UInt32, state: VMState) async -> ExecOutcome {
if index == GasFn.identifier {
switch UInt8(index) {
case GasFn.identifier:

Check warning on line 19 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift#L18-L19

Added lines #L18 - L19 were not covered by tests
return await GasFn().call(config: config, state: state)
} else {
case Log.identifier:
return await Log().call(config: config, state: state)
default:

Check warning on line 23 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift#L21-L23

Added lines #L21 - L23 were not covered by tests
state.consumeGas(Gas(10))
state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHAT.rawValue)
return .continued
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
case Info.identifier:
return await Info(serviceIndex: context.index, accounts: context.accounts)
.call(config: config, state: state)
case Log.identifier:
return await Log(service: index).call(config: config, state: state)

Check warning on line 39 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/OnTransferContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/OnTransferContext.swift#L38-L39

Added lines #L38 - L39 were not covered by tests
default:
state.consumeGas(Gas(10))
state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHAT.rawValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,35 +45,38 @@
public func dispatch(index: UInt32, state: VMState) async -> ExecOutcome {
logger.debug("dispatching host-call: \(index)")

if index == GasFn.identifier {
switch UInt8(index) {
case GasFn.identifier:

Check warning on line 49 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L48-L49

Added lines #L48 - L49 were not covered by tests
return await GasFn().call(config: config, state: state)
} else if index == HistoricalLookup.identifier {
case HistoricalLookup.identifier:

Check warning on line 51 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L51

Added line #L51 was not covered by tests
return await HistoricalLookup(
context: context,
service: service,
serviceAccounts: serviceAccounts,
lookupAnchorTimeslot: lookupAnchorTimeslot
)
.call(config: config, state: state)
} else if index == Import.identifier {
case Import.identifier:

Check warning on line 59 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L59

Added line #L59 was not covered by tests
return await Import(context: context, importSegments: importSegments).call(config: config, state: state)
} else if index == Export.identifier {
case Export.identifier:

Check warning on line 61 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L61

Added line #L61 was not covered by tests
return await Export(context: &context, exportSegmentOffset: exportSegmentOffset).call(config: config, state: state)
} else if index == Machine.identifier {
case Machine.identifier:

Check warning on line 63 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L63

Added line #L63 was not covered by tests
return await Machine(context: &context).call(config: config, state: state)
} else if index == Peek.identifier {
case Peek.identifier:

Check warning on line 65 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L65

Added line #L65 was not covered by tests
return await Peek(context: context).call(config: config, state: state)
} else if index == Zero.identifier {
case Zero.identifier:

Check warning on line 67 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L67

Added line #L67 was not covered by tests
return await Zero(context: &context).call(config: config, state: state)
} else if index == Poke.identifier {
case Poke.identifier:

Check warning on line 69 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L69

Added line #L69 was not covered by tests
return await Poke(context: &context).call(config: config, state: state)
} else if index == VoidFn.identifier {
case VoidFn.identifier:

Check warning on line 71 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L71

Added line #L71 was not covered by tests
return await VoidFn(context: &context).call(config: config, state: state)
} else if index == Invoke.identifier {
case Invoke.identifier:

Check warning on line 73 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L73

Added line #L73 was not covered by tests
return await Invoke(context: &context).call(config: config, state: state)
} else if index == Expunge.identifier {
case Expunge.identifier:

Check warning on line 75 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L75

Added line #L75 was not covered by tests
return await Expunge(context: &context).call(config: config, state: state)
} else {
case Log.identifier:
return await Log(service: service).call(config: config, state: state)
default:

Check warning on line 79 in Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/RefineContext.swift#L77-L79

Added lines #L77 - L79 were not covered by tests
state.consumeGas(Gas(10))
state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHAT.rawValue)
return .continued
Expand Down
52 changes: 52 additions & 0 deletions Blockchain/Tests/BlockchainTests/VMInvocations/LogTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Foundation
import Testing
import Utils

@testable import Blockchain

struct LogTests {
@Test func testLogDetailJSON() async throws {
let logDetials = Log.Details(
time: "2023-04-01 12:00:00",
level: .error,
target: Data("target".utf8),
message: Data("message".utf8),
core: nil,
service: 1
)
let json = logDetials.json
#expect(json["time"]?.string == "2023-04-01 12:00:00")
#expect(json["level"]?.string == "ERROR")
#expect(json["target"]?.string == "target")
#expect(json["message"]?.string == "message")
#expect(json["service"]?.string == "1")
#expect(json["core"] == .null)
}

@Test func testLogDetailString() async throws {
let logDetials = Log.Details(
time: "2023-04-01 12:00:00",
level: .trace,
target: Data("target".utf8),
message: Data("message".utf8),
core: nil,
service: nil
)
let str = logDetials.str
#expect(str == "2023-04-01 12:00:00 TRACE target message")
}

@Test func testLogDetailInvalidString() async throws {
let invalidData = Data([0xFF, 0xFE, 0xFD])
let logDetails = Log.Details(
time: "2023-04-01 12:00:00",
level: .warn,
target: invalidData,
message: invalidData,
core: nil,
service: nil
)
let str = logDetails.str
#expect(str == "2023-04-01 12:00:00 WARN invalid string invalid string")
}
}