Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Простые определяемые пользователем функции (UDFS) в конфиденциальном реестре Azure позволяют создавать пользовательские функции JavaScript, которые можно выполнять внутри границы доверия реестра. Эта функция предназначена для простого и простого использования, что позволяет расширить функциональные возможности API реестра без необходимости сложной разработки приложений.
С помощью встроенного API JavaScript можно запустить пользовательский код для выполнения различных задач, таких как пользовательские запросы и вычисления, условные проверки, задачи после обработки и многое другое. Эта функция подходит для сценариев, где требуется прямая интеграция с существующим API реестра или выполнение упрощенной пользовательской логики в конфиденциальной среде.
Краткий обзор и демонстрацию определяемых пользователем функций смотрите в следующем видео.
Это важно
Определяемые пользователем функции в настоящее время находятся в предварительной версии 2024-12-09-preview
API.
Вы можете запросить доступ к этой предварительной версии с помощью этой формы регистрации.
Обратитесь к дополнительным условиям использования предварительных версий Microsoft Azure для получения информации о юридических условиях, которые применяются к функциям Azure, находящимся в бета-версии, предварительной версии или еще не выпущенным в широкую доступность.
Подсказка
Для более сложных сценариев, таких как пользовательское управление доступом (RBAC) Role-Based или интеграция с внешними конфиденциальными рабочими нагрузками, см. в разделе о расширенных пользовательских функциях в конфиденциальном реестре Azure.
Случаи использования
Определяемые пользователем функции (UDF) в Azure Confidential Ledger позволяют расширить функциональные возможности реестра, выполняя пользовательскую логику. Некоторые общие примеры использования определяемых пользователем функций включают:
Пользовательские вычисления и запросы: запускайте автономные пользовательские функции для чтения или записи данных в любую таблицу приложения реестра в соответствии с вашей бизнес-логикой.
Проверка и валидация входных данных: используйте пользовательские функции в качестве предварительных обработчиков для выполнения действий перед записью в реестр, например для очистки входящих данных или проверки предварительных условий.
Обогащение данных и смарт-контракты: используйте UDFs в качестве хуков для выполнения действий после записи в реестр, например, чтобы добавить пользовательские метаданные в реестр или запустить рабочие процессы после записи.
Написание определяемых пользователем функций
UDF конфиденциального реестра Azure — это сущность, хранящуюся в реестре с уникальным идентификатором, и содержит код JavaScript, который выполняется при вызове UDF. В этом разделе описывается написание кода UDF и использование API JavaScript для достижения различных задач.
Структура функций
Код UDF требует экспортируемой функции, которая является точкой входа скрипта во время выполнения. Базовый шаблон кода UDF выглядит следующим образом:
export function main() {
// Your JavaScript code here
}
Замечание
Имя экспортируемой функции точки входа, вызываемой во время выполнения, можно изменить с аргументом exportedFunctionName
при запуске UDF. Если значение не указано, то используется main
имя по умолчанию.
Замечание
Лямбда-функции поддерживаются, но они требуют явного определения экспортированного имени функции и сопоставления имени функции точки входа. Рассмотрим пример.
export const main = () => {
// Your JavaScript code here
};
Аргументы функции
Можно указать любые необязательные аргументы среды выполнения, принятые UDF. Значения аргументов можно передать во время выполнения при запуске UDF с помощью arguments
параметра.
Аргументы всегда передаются в виде массива строк. Ответственность пользователя заключается в том, чтобы убедиться, что аргументы, указанные в коде UDF, соответствуют аргументам, переданным при запуске UDF. Пользователь также должен убедиться, что аргументы правильно проанализированы с ожидаемым типом данных во время выполнения.
export function main(arg1, arg2) {
// Your JavaScript code here
}
API JavaScript
Код JavaScript UDF выполняется в изолированной среде, которая предоставляет ограниченный набор API.
Можно использовать все стандартные глобальные функции, объекты и значения JavaScript . Глобальный объект ccf
можно использовать для доступа к определенным функциям и служебным программам, предоставляемым платформой конфиденциального консорциума (CCF) (например, вспомогательными функциями криптографии, средствами доступа к таблицам реестра и т. д.). Полный ccf
API глобального объекта описан здесь.
Вы также можете получить доступ к контекстной информации текущего запроса с помощью глобального context
объекта. Этот объект предоставляет доступ к метаданным запроса, которые вызвали выполнение функции (context.request
) и идентификатор пользователя вызывающего функции (context.userId
). Для хуков транзакций идентификатор коллекции и содержимое транзакции, связанные с операцией записи, также добавляются в объект context
(context.collectionId
и context.contents
соответственно).
В следующем фрагменте кода показаны некоторые основные примеры использования API JavaScript:
export function main(args) {
// Basic instructions
const a = 1 + 1;
// Basic statements
if (a > 0) {
console.log("a is positive");
} else {
console.log("a is negative or zero");
}
// Parse the string argument as a JSON object
JSON.parse(args);
// Logging utilities
console.log("Hello world");
// Math utilities
Math.random();
// CCF cryptography utilities
ccf.crypto.digest("SHA-256", ccf.strToBuf("Hello world"));
// Write to a custom ledger table
ccf.kv["public:mytable"].set(ccf.strToBuf("myKey"), ccf.strToBuf("myValue"));
// Read from a custom ledger table
ccf.bufToStr(ccf.kv["public:mytable"].get(ccf.strToBuf("myKey")));
// Read from the ledger entry table
ccf.kv["public:confidentialledger.logs"].get(ccf.strToBuf("subledger:0"));
// Get the request metadata that originated the function execution
const requestMetadata = context.request;
// Get the collection ID and transaction content (for transaction hooks only)
const collectionId = context.collectionId;
const contents = context.contents;
// Throw exceptions
throw new Error("MyCustomError");
}
Подсказка
Дополнительные сведения о том, как карты реестра можно использовать для хранения и извлечения данных, см. в документации по CCF в API хранилища Key-Value.
Замечание
Импорт модулей не поддерживается в определяемых пользователем функциях. Код JavaScript должен быть автономным и не может полагаться на внешние библиотеки или модули. Веб-API также не поддерживаются в данный момент.
Управление определяемыми пользователем функциями
Приложения конфиденциального реестра Azure предоставляют выделенный API CRUD для создания, чтения, обновления и удаления сущностей UDF. UDF безопасно хранятся в реестре и доступны только приложению реестра.
Замечание
Простые пользовательские функции и расширенные пользовательские функции являются взаимоисключающими функциями. Невозможно создать или запустить простые UDF, если созданы расширенные UDF, и наоборот. Чтобы переключиться между этими двумя, следуйте инструкциям на странице обзора UDF.
Создание или обновление UDF
PUT /app/userDefinedFunctions/myFunction
{
"code": "export function main() { return "Hello World"; }",
}
Это важно
Роль администратора требуется для создания или обновления UDF.
Получите UDF
GET /app/userDefinedFunctions/myFunction
Список пользовательских функций
GET /app/userDefinedFunctions
Удалите UDF
DELETE /app/userDefinedFunctions/myFunction
Это важно
Роль администратора требуется для удаления UDF.
Замечание
При удалении UDF удаляется только сущность из текущего состояния реестра. Любой удаленный UDF всегда сохраняется в неизменяемом журнале реестра (как любая зафиксированная транзакция).
Запуск определяемых пользователем функций
После создания пользователи конфиденциального реестра Azure могут выполнять UDF как автономную функцию или как перехватчик транзакций, связанный с операцией записи. Каждое выполнение определяемой пользователем функции (UDF) выполняется в отдельной среде выполнения и песочнице, что означает, что выполнение UDF изолировано от других определяемых пользователем функций или операций реестра.
Выполнение UDF можно контролировать с помощью необязательных свойств, которые можно указать в тексте запроса. Поддерживаемые в настоящее время свойства:
arguments
: массив строк, представляющих аргументы, передаваемые в UDF. Аргументы передаются в том же порядке, что и в коде UDF. Значение по умолчанию — пустой массив.exportedFunctionName
: имя экспортируемой функции, вызываемой во время выполнения. Если не задано, по умолчанию используется значениеmain
.runtimeOptions
: объект, указывающий параметры среды выполнения для выполнения UDF. Имеются следующие варианты:max_heap_bytes
: максимальный размер кучи в байтах. Значение по умолчанию — 10 485 760 (10 МБ).max_stack_bytes
: максимальный размер стека в байтах. Значение по умолчанию — 1 048 576 (1 МБ).max_execution_time_ms
: максимальное время выполнения в миллисекундах. Значение по умолчанию — 1000 (1 секунда).log_exception_details
: логическое значение, указывающее, следует ли регистрировать сведения об исключении. Значение по умолчанию —true
.return_exception_details
: логическое значение, указывающее, следует ли возвращать сведения об исключении в ответе. Значение по умолчанию —true
.
Автономные функции
UDF можно выполнять напрямую с помощью POST /app/userDefinedFunctions/{functionId}:execute
API.
POST /app/userDefinedFunctions/myFunction:execute
{}
Текст запроса можно использовать для указания необязательных параметров выполнения, таких как аргументы функций и свойства среды выполнения JavaScript.
POST /app/userDefinedFunctions/myFunction:execute
{
"arguments": ["arg1", "arg2"],
"exportedFunctionName": "myMainFunction",
"runtimeOptions": {
"max_heap_bytes": 5,
"max_stack_bytes": 1024,
"max_execution_time_ms": 5000,
"log_exception_details": true,
"return_exception_details": true
}
}
Ответ указывает результат выполнения UDF (успешно или неудачно). Если UDF выполнен успешно, ответ включает возвращаемое функцией значение в строковом формате (если таковой имеется).
{
"result":
{
"returnValue": "MyReturnValue"
},
"status": "Succeeded"
}
Если не удалось выполнить UDF, ответ содержит сообщение об ошибке с подробной трассировкой стека.
{
"error": {
"message": "Error while executing function myFunction: Error: MyCustomError\n at myMainFunction (myFunction)\n"
},
"status": "Failed"
}
Это важно
Роль участника требуется для выполнения UDF.
Перехватчики транзакций
Кроме того, UDF можно выполнять как перехватчик до (предхук) или после (постхук) внесения записи в реестр в рамках API записи в реестр (POST /app/transactions
). Хуки выполняются в том же контексте операции записи; это означает, что все данные, записанные в реестр хуками, автоматически включаются в ту же транзакцию записи.
Текст запроса на запись можно использовать для указания всех идентификаторов UDF, которые будут выполняться в качестве предварительных перехватчиков и после перехватчиков соответственно.
POST /app/transactions?collectionId=myCollection
{
"contents": "myValue",
"preHooks": [
{
"functionId": "myPreHook"
}
],
"postHooks": [
{
"functionId": "myPostHook"
}
]
}
Это важно
Хуки должны быть явно определены в тексте запроса операции записи. Как правило, после создания пользовательские определяемые функции не могут выполняться автоматически при каждой операции записи.
Для каждого хука можно указать любые дополнительные свойства выполнения. Рассмотрим пример.
POST /app/transactions?collectionId=myCollection
{
"contents": "myValue",
"preHooks": [
{
"functionId": "myPreHook",
"properties": {
"arguments": [
"arg1",
"arg2"
],
"exportedFunctionName": "myMainFunction",
"runtimeOptions": {
"max_heap_bytes": 5,
"max_stack_bytes": 1024,
"max_execution_time_ms": 5000,
"log_exception_details": true,
"return_exception_details": true
}
}
}
],
"postHooks": [
{
"functionId": "myPostHook",
"properties": {
"arguments": [
"arg1"
],
"exportedFunctionName": "myMainFunction",
"runtimeOptions": {
"max_heap_bytes": 5,
"max_stack_bytes": 1024,
"max_execution_time_ms": 5000,
"log_exception_details": true,
"return_exception_details": true
}
}
}
]
}
Вы можете указать до 5 предварительных перехватчиков и после перехватчиков в тексте запроса с любым сочетанием. Перехватчики всегда выполняются в порядке, в котором они указаны в тексте запроса.
Если предварительный хук или последующий хук дает сбой, вся транзакция прерывается. В этом случае ответ содержит сообщение об ошибке с причиной сбоя:
{
"error": {
"code": "InternalError",
"message": "Error while executing function myPreHook: Error: MyCustomError\n at myMainFunction (myPreHook)\n",
}
}
Замечание
Даже если несколько хуков успешно завершены, транзакция всё равно может завершиться ошибкой, если любой из определённых pre-хуков или post-хуков не выполняется успешно до конца.
Подсказка
UDF можно повторно использовать в качестве предварительного и постперехватчика в одном запросе и вызывать несколько раз.
Примеры
В этом разделе рассматриваются некоторые практические примеры использования объектно-определяемых функций (UDF) в конфиденциальном реестре Azure. В следующих примерах сценариев предполагается использование конфиденциального реестра Azure для хранения банковских транзакций для разных пользователей банка.
Контекст
Для хранения банковских транзакций для пользователя можно использовать существующий API записи реестра: значение транзакции — это содержимое записи реестра, а идентификатор пользователя может быть коллекцией или ключом, в котором содержимое записывается.
POST /app/transactions?collectionId=John
{
"contents": "10"
}
HTTP/1.1 200 OK
Поскольку для вводимого содержимого отсутствует проверка, можно ввести нечисловое значение. Например, этот запрос завершается успешно, даже если значение содержимого не является числом:
POST /app/transactions?collectionId=Mark
{
"contents": "This is not a number"
}
HTTP/1.1 200 OK
Предварительные перехватчики для проверки данных
Чтобы содержимое транзакции всегда было числом, можно создать UDF для проверки входного содержимого. Следующий предварительный перехватчик проверяет, является ли значение содержимого числом, и выдает ошибку, если нет.
PUT /app/userDefinedFunctions/validateTransaction
{
"code": "export function main() { if (isNaN(context.contents)) { throw new Error('Contents is not a number'); } }"
}
HTTP/1.1 201 CREATED
Используя предварительный хук в запросе на запись, можно гарантировать, что входные данные соответствуют ожидаемому формату. Предыдущий запрос теперь выдает ошибку, как и ожидалось.
POST /app/transactions?collectionId=Mark
{
"contents": "This is not a number",
"preHooks": [
{
"functionId": "validateTransaction"
}
]
}
HTTP/1.1 500 INTERNAL_SERVER_ERROR
{
"error": {
"code": "InternalError",
"message": "Error while executing function validateTransaction: Error: Contents is not a number\n at main (validateTransaction)\n"
}
}
Допустимые запросы, содержащие числовые значения, будут выполнены успешно, как ожидалось:
POST /app/transactions?collectionId=Mark
{
"contents": "30",
"preHooks": [
{
"functionId": "validateTransaction"
}
]
}
HTTP/1.1 200 OK
Пост-хуки для обогащения данных
По мере того как пользователи выполняют новые банковские транзакции, мы хотим записать, когда транзакция выше определенного порогового значения по причинам аудита. Пост-хук можно использовать для записи пользовательских метаданных в реестре после выполнения операции записи, чтобы указать, была ли транзакция выше определенного порогового значения.
Например, можно создать UDF, чтобы проверить значение транзакции и записать фиктивное сообщение ("Оповещение" для высоких значений, "Обычный" в противном случае) под входным пользователем в настраиваемую таблицу реестра (payment_metadata
), если значение превышает 50.
PUT /app/userDefinedFunctions/detectHighTransaction
{
"code": "export function main() { let value = 'Normal'; if (context.contents > 50) { value = 'Alert' } ccf.kv['public:payment_metadata'].set(ccf.strToBuf(context.collectionId), ccf.strToBuf(value)); }"
}
HTTP/1.1 201 CREATED
После успешного создания UDF перехватчик можно использовать в новых запросах на запись.
POST /app/transactions?collectionId=Mark
{
"contents": "100",
"preHooks": [
{
"functionId": "validateTransaction"
}
],
"postHooks": [
{
"functionId": "detectHighTransaction"
}
]
}
HTTP/1.1 200 OK
POST /app/transactions?collectionId=John
{
"contents": "20",
"preHooks": [
{
"functionId": "validateTransaction"
}
],
"postHooks": [
{
"functionId": "detectHighTransaction"
}
]
}
HTTP/1.1 200 OK
Автономные определяемые пользователем функции для пользовательских запросов
Чтобы проверить последние значения, записанные в настраиваемую таблицу payment_metadata
с помощью пост-хука, можно создать UDF для чтения значений из таблицы с пользовательским ID:
PUT /app/userDefinedFunctions/checkPaymentMetadataTable
{
"code": "export function main(user) { const value = ccf.kv['public:payment_metadata'].get(ccf.strToBuf(user)); if (value === undefined) { throw new Error('UnknownUser'); } return ccf.bufToStr(value); }"
}
HTTP/1.1 201 CREATED
Запустив UDF напрямую, можно проверить последнее значение, записанное в пользовательской таблице метаданных для данного пользователя.
Для пользователей с недавней высокой транзакцией UDF возвращает значение "Оповещение", как и ожидалось.
POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
"arguments": [
"Mark"
]
}
HTTP/1.1 200 OK
{
"result": {
"returnValue": "Alert"
},
"status": "Succeeded"
}
Для пользователей с последней низкой транзакцией UDF возвращает вместо этого значение "Обычный".
POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
"arguments": [
"John"
]
}
HTTP/1.1 200 OK
{
"result": {
"returnValue": "Normal"
},
"status": "Succeeded"
}
Для пользователей, у которых нет записи в пользовательской таблице, UDF выдает ошибку, определенную в коде UDF.
POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
"arguments": [
"Jane"
]
}
HTTP/1.1 200 OK
{
"error": {
"message": "Error while executing function checkPaymentMetadataTable: Error: UnknownUser\n at main (checkPaymentMetadataTable)\n"
},
"status": "Failed"
}
Соображения
В настоящее время транзакционные хуки поддерживаются только для
POST /app/transactions
API при добавлении новой записи в бухгалтерскую книгу.Определяемые пользователем функции и хуки всегда выполняются на первичной реплике реестра, чтобы обеспечить упорядочение транзакций и строгую согласованность.
Выполнение кода UDF всегда упаковывается в одну атомарную транзакцию. Если логика JavaScript в UDF завершается без каких-либо исключений, все операции в UDF фиксируются в реестре. Если возникает исключение, все транзакции откатываются. Аналогичным образом, прехуки и постхуки выполняются в том же контексте операции записи, на которую они зарегистрированы. Если предварительный перехватчик или после перехватчика завершается сбоем, вся транзакция прервана и запись не добавляется в реестр.
Пользовательские функции могут получить доступ только к таблицам приложения CCF и не могут получить доступ к внутренним таблицам реестра и таблицам управления или другим встроенным таблицам по соображениям безопасности. Таблицы реестра, в которых записи записываются (
public:confidentialledger.logs
для общедоступных реестров иprivate:confidentialledger.logs
для частных реестров) доступны только для чтения.Максимальное количество пре-хуков и пост-хуков, которые можно зарегистрировать для одной транзакции записи, равно 5.
Выполнение UDF и хуков ограничено 5 секундами. Если для выполнения функции требуется более 5 секунд, операция прерывается и возвращается ошибка.