Funzioni definite dall'utente in Azure Confidential Ledger

Le funzioni definite dall'utente (UDF) in Azure Confidential Ledger consentono di creare funzioni JavaScript personalizzate eseguite all'interno del limite di attendibilità del libro mastro. Questa funzionalità è semplice e facile da usare, quindi è possibile estendere la funzionalità dell'API ledger senza sviluppo di applicazioni complesse. Le funzioni definite dall'utente erano precedentemente chiamate funzioni semplici definite dall'utente.

Usando l'API JavaScript predefinita, è possibile eseguire codice personalizzato per ottenere varie attività, ad esempio query e calcoli personalizzati, controlli condizionali, attività di post-elaborazione e altro ancora. Questa funzionalità è adatta agli scenari in cui è necessaria un'integrazione diretta con l'API libro mastro esistente o eseguire logica personalizzata leggera in un ambiente riservato.

Per una rapida panoramica e una demo delle UDF, guardare il video seguente:

Annotazioni

Per usare le funzioni definite dall'utente, usare la versione 2026-02-23 dell'API REST o versioni successive.

Suggerimento

Per scenari più avanzati, ad esempio Role-Based Controllo di accesso personalizzati o integrazione con carichi di lavoro riservati esterni, vedere Endpoint definiti dall'utente in Azure Confidential Ledger.

Casi d'uso

Le UDF del libro mastro riservato di Azure consentono di estendere la funzionalità del libro mastro eseguendo la logica personalizzata del libro mastro. Alcuni casi d'uso comuni delle UDF includono:

  • Calcoli e query personalizzati: eseguire UDF autonome per leggere o scrivere dati in qualsiasi tabella dell'applicazione di libro mastro secondo la logica aziendale.

  • Controlli di convalida e input dei dati: usare le UDF come pre-hook per eseguire azioni di pre-elaborazione prima che una voce venga registrata nel libro mastro, ad esempio per pulire i dati di input o verificare che le pre-condizioni siano soddisfatte.

  • Arricchimento dei dati e contratti intelligenti: usare le UDF come post-hook per eseguire azioni di post-elaborazione dopo la scrittura di una voce libro mastro, ad esempio per aggiungere metadati personalizzati nel libro mastro o attivare flussi di lavoro post-scrittura.

Scrittura di UDF

Una UDF di Azure Confidential Ledger è un'entità archiviata nel libro mastro con un ID univoco e contiene il codice JavaScript eseguito quando viene chiamata la UDF. Questa sezione descrive come scrivere codice UDF e usare l'API JavaScript per ottenere attività diverse.

Struttura della funzione

Il codice di una UDF (Funzione Definita dall'Utente) richiede una funzione esportata che rappresenta il punto di ingresso dello script al momento dell'esecuzione. Un modello di codice UDF di base è simile al seguente:

export function main() {
    // Your JavaScript code here
}

Annotazioni

Il nome della funzione del punto di ingresso esportata, chiamata durante l'esecuzione, può essere modificato con l'argomento exportedFunctionName quando si esegue la UDF. Se non specificato, il nome predefinito è main.

Annotazioni

Le funzioni lambda sono supportate, ma richiedono che il nome della funzione esportata sia definito in modo esplicito e corrisponda al nome della funzione del punto di ingresso. Per esempio:

export const main = () => { 
    // Your JavaScript code here 
};

Argomenti di funzione

È possibile specificare qualsiasi argomento di runtime facoltativo accettato dalla UDF. I valori degli argomenti possono essere passati in fase di esecuzione della UDF utilizzando il parametro arguments.

Gli argomenti vengono sempre passati come una matrice di stringhe. È responsabilità dell'utente assicurarsi che gli argomenti specificati nel codice UDF corrispondano agli argomenti passati al momento dell'esecuzione dell'UDF. L'utente deve anche assicurarsi che gli argomenti vengano analizzati correttamente nel tipo di dati previsto in fase di esecuzione.

export function main(arg1, arg2) {
    // Your JavaScript code here
}

API JavaScript

Il codice JavaScript di una UDF viene eseguito all'interno di un ambiente sandbox che fornisce un set limitato di API.

È possibile usare tutte le funzioni globali, gli oggetti e i valori standard javaScript. È anche possibile usare l'oggetto globale ccf per accedere a funzionalità e utilità specifiche fornite da Confidential Consortium Framework (CCF), ad esempio funzioni helper di crittografia e funzioni di accesso tabella mastro. Il riferimento all'interfaccia CCF documenta l'API completa dell'oggetto ccf globale.

È anche possibile accedere alle informazioni contestuali della richiesta corrente usando l'oggetto context globale. Questo oggetto fornisce l'accesso ai metadati della richiesta che hanno originato l'esecuzione della funzione (context.request) e l'ID utente del chiamante di funzione (context.userId). Per gli hook delle transazioni, anche l'ID raccolta e il contenuto della transazione associati all'operazione di scrittura vengono aggiunti all'oggetto context (context.collectionId e context.contents rispettivamente).

Il frammento di codice seguente illustra alcuni esempi di base dell'uso dell'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");
}

Suggerimento

Per altre informazioni sull'uso delle mappe mastro per archiviare e recuperare i dati, vedere la documentazione CCF sull'API di Key-Value Store.

Annotazioni

L'importazione dei moduli non è supportata nelle UDF. Il codice JavaScript deve essere indipendente e non può basarsi su librerie o moduli esterni. Anche le API Web non sono supportate al momento.

Gestione delle UDF

Le applicazioni del registro riservato di Azure forniscono un'API CRUD dedicata per creare, leggere, aggiornare ed eliminare le entità UDF. Le UDF vengono archiviate in modo sicuro nel libro mastro e sono accessibili solo all'applicazione del libro mastro.

Annotazioni

Le funzioni definite dall'utente e gli endpoint definiti dall'utente sono funzionalità che si escludono a vicenda. Non è possibile creare o eseguire UDF se sono definiti UDE e viceversa. Per passare da una all'altra, seguire le istruzioni riportate in Passaggio tra funzioni definite dall'utente ed endpoint definiti dall'utente.

Creare o aggiornare una UDF

PUT /app/userDefinedFunctions/myFunction
{
    "code": "export function main() { return 'Hello World'; }"
}

Importante

Per creare o aggiornare una UDF, è necessario il ruolo di amministratore.

Ricevere una UDF

GET /app/userDefinedFunctions/myFunction

Elenco delle UDF

GET /app/userDefinedFunctions

Eliminare un UDF

DELETE /app/userDefinedFunctions/myFunction

Importante

Per eliminare una UDF, è necessario il ruolo di amministratore.

Annotazioni

L'eliminazione di una UDF rimuove solo l'entità dallo stato corrente del libro mastro. Qualsiasi UDF eliminata viene sempre conservata nella cronologia del libro mastro immutabile, come qualsiasi transazione di cui è stato eseguito il commit.

Limits

Azure Confidential Ledger limita il numero di funzioni definite dall'utente in un libro mastro, la lunghezza degli ID UDF e le dimensioni del codice UDF. Per i limiti correnti e il modo in cui vengono applicati, vedere limiti di Azure Confidential Ledger.

UDF in esecuzione

Dopo la creazione, gli utenti di Azure Confidential Ledger possono eseguire una UDF come funzione autonoma o come hook di transazione associato a un'operazione di scrittura. Ogni esecuzione di una UDF avviene in un ambiente di runtime e in una sandbox separati, il che significa che l'esecuzione della UDF è isolata da altre UDF o da altre operazioni del registro Ledger.

L'esecuzione della UDF può essere controllata tramite proprietà opzionali che possono essere specificate nel corpo della richiesta. Le proprietà attualmente supportate sono:

  • arguments: matrice di stringhe che rappresentano gli argomenti da passare all'UDF. Gli argomenti vengono passati nello stesso ordine in cui sono definiti nel codice UDF. Il valore predefinito è una matrice vuota.

  • exportedFunctionName: nome della funzione esportata da chiamare durante l'esecuzione. Se non è specificato, il valore predefinito è main.

  • runtimeOptions: oggetto che specifica le opzioni di runtime per l'esecuzione di funzioni definite dall'utente, descritte nella tabella seguente.

Option Description Predefinito Valori consentiti
max_heap_bytes Dimensione massima dell'heap in byte. 104.857.600 (100 MiB) Le dimensioni dell'heap sono attualmente fisse a 100 MiB. Gli altri valori non lo cambiano.
max_stack_bytes Dimensioni massime dello stack in byte. 1.048.576 (1 MiB) Da 1.048.576 a 10.485.760 (10 MiB)
max_execution_time_ms Il tempo massimo di esecuzione in millisecondi. 5.000 (5 secondi) da 5.000 a 10.000 (10 secondi)
log_exception_details Se registrare i dettagli dell'eccezione. false true oppure false
return_exception_details Se restituire i dettagli dell'eccezione nella risposta. true true oppure false

I valori non compresi nell'intervallo consentito non causano un errore. Il libro mastro usa invece il valore consentito più vicino.

Funzioni autonome

Una UDF può essere eseguita direttamente usando l'API POST /app/userDefinedFunctions/{functionId}:execute.

POST /app/userDefinedFunctions/myFunction:execute
{}

Il corpo della richiesta può essere usato per specificare parametri di esecuzione facoltativi, ad esempio argomenti di funzione e proprietà di runtime JavaScript.

POST /app/userDefinedFunctions/myFunction:execute
{
    "arguments": ["arg1", "arg2"],
    "exportedFunctionName": "myMainFunction",
    "runtimeOptions": {
        "max_stack_bytes": 2097152,
        "max_execution_time_ms": 8000,
        "log_exception_details": true,
        "return_exception_details": true
    }
}

La risposta indica il risultato dell'esecuzione dell'UDF (riuscita o fallita). Se l'UDF ha avuto esito positivo, la risposta include il valore restituito dalla funzione in formato stringa (se disponibile).

{
    "result": 
        {
            "returnValue": "MyReturnValue"
        }, 
    "status": "Succeeded"
}

Se la funzione UDF fallisce, la risposta include il messaggio di errore con la traccia dettagliata dello stack.

{
    "error": {
        "message": "Error while executing function myFunction: Error: MyCustomError\n    at myMainFunction (myFunction)\n"
    }, 
    "status": "Failed"
}

Importante

È necessario il ruolo di collaboratore per eseguire una UDF.

Hook delle transazioni

Una UDF può essere in alternativa eseguita come hook prima (pre-hook) o dopo (post-hook) una voce viene scritta nel libro mastro come parte dell'API di scrittura del libro mastro (POST /app/transactions). Gli hook vengono eseguiti nello stesso contesto dell'operazione di scrittura; ciò significa che tutti i dati scritti nel libro mastro dagli hook vengono inclusi automaticamente nella stessa transazione di scrittura.

Il corpo della richiesta di scrittura può essere usato per specificare gli ID UDF da eseguire rispettivamente come pre-hook e post-hook.

POST /app/transactions?collectionId=myCollection
{
  "contents": "myValue", 
  "preHooks": [ 
    { 
        "functionId": "myPreHook"
    } 
  ], 
  "postHooks": [ 
    { 
        "functionId": "myPostHook" 
    }
  ] 
} 

Importante

Gli hook devono essere definiti in modo esplicito nel corpo della richiesta dell'operazione di scrittura. In generale, le UDF non possono essere eseguite automaticamente per ogni operazione di scrittura dopo essere state create.

Per ogni hook, è possibile specificare le proprietà di esecuzione facoltative. Per esempio:

POST /app/transactions?collectionId=myCollection
{
  "contents": "myValue", 
  "preHooks": [ 
    { 
        "functionId": "myPreHook", 
        "properties": { 
            "arguments": [ 
                "arg1",
                "arg2"
            ], 
            "exportedFunctionName": "myMainFunction", 
            "runtimeOptions": { 
                "max_stack_bytes": 2097152,
                "max_execution_time_ms": 8000,
                "log_exception_details": true,
                "return_exception_details": true
            } 
        } 
    } 
  ], 
  "postHooks": [ 
    { 
        "functionId": "myPostHook", 
        "properties": { 
            "arguments": [ 
                "arg1"
            ], 
            "exportedFunctionName": "myMainFunction", 
            "runtimeOptions": { 
                "max_stack_bytes": 2097152,
                "max_execution_time_ms": 8000,
                "log_exception_details": true,
                "return_exception_details": true
            } 
        } 
    }
  ] 
} 

Gli hook vengono sempre eseguiti nell'ordine in cui vengono forniti nel corpo della richiesta. Il numero di pre-hook e post-hook in una richiesta è limitato. Per i limiti correnti, vedere limiti di Azure Confidential Ledger.

Se un pre-hook o un post-hook fallisce, l'intera transazione viene annullata. In tal caso, la risposta contiene il messaggio di errore con il motivo dell'errore:

{
    "error": {
        "code": "InternalError",
        "message": "Error while executing function myPreHook: Error: MyCustomError\n    at myMainFunction (myPreHook)\n"
    }
}

Annotazioni

Anche se più hook hanno esito positivo, la transazione può comunque fallire se uno dei pre-hook o post-hook definiti non viene completato correttamente.

Suggerimento

Una UDF può essere riutilizzata come pre-hook e post-hook nella stessa richiesta e chiamata più volte.

Esempi

Questa sezione illustra alcuni esempi pratici di come usare le UDF in Azure Confidential Ledger. Gli scenari di esempio seguenti presuppongono che si usi Azure confidential ledger per archiviare le transazioni bancarie per utenti bancari diversi.

Contesto

Per archiviare una transazione bancaria per un utente, è possibile usare l'API di scrittura del libro mastro esistente: il valore della transazione è il contenuto della voce libro mastro e l'ID utente può essere la raccolta o la chiave, in cui il contenuto è scritto.

POST /app/transactions?collectionId=John
{
    "contents": "10"
}

HTTP/1.1 200 OK

Poiché non esiste alcuna convalida sul contenuto di input, è possibile scrivere un valore non numerico come contenuto. Ad esempio, questa richiesta ha esito positivo anche se il valore del contenuto non è un numero:

POST /app/transactions?collectionId=Mark
{
    "contents": "This is not a number"
}

HTTP/1.1 200 OK

Pre-hook per la convalida dei dati

Per assicurarsi che il contenuto della transazione sia sempre un numero, creare una funzione definita dall'utente per controllare il contenuto dell'input. Il pre-hook seguente controlla se il valore di contents è un numero e genera un errore se non lo è.

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

Usando il pre-hook nella richiesta di scrittura, è possibile imporre che i dati di input corrispondano al formato previsto. La richiesta precedente ha ora esito negativo come previsto:

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"
  }
}

Le richieste valide contenenti valori numerici hanno invece esito positivo come previsto:

POST /app/transactions?collectionId=Mark
{
    "contents": "30",
    "preHooks": [
        {
            "functionId": "validateTransaction"
        }
    ]
}

HTTP/1.1 200 OK

Post-hook per l'arricchimento dei dati

Quando gli utenti eseguono nuove transazioni bancarie, si vuole registrare quando una transazione è superiore a una determinata soglia per motivi di controllo. Un post-hook può essere utilizzato per scrivere metadati personalizzati in un libro mastro dopo un'operazione di scrittura per indicare se la transazione è superiore a una soglia specifica.

Ad esempio, è possibile creare una UDF per controllare il valore della transazione e inserire un messaggio simbolico ("Avviso" per valori elevati, "Normale" in caso contrario) nella tabella libro mastro personalizzata dell'utente di input (payment_metadata) se il valore della transazione è superiore a 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

Una volta che la UDF è stata creata con successo, il post-hook può essere utilizzato nelle nuove richieste di scrittura:

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

UDF autonome per query personalizzate

Per esaminare i valori più recenti inseriti nella tabella personalizzata payment_metadata utilizzando il post-hook, è possibile creare una UDF per leggere i valori dalla tabella in base a un ID utente fornito.

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

Eseguendo direttamente l'UDF, è possibile verificare il valore più recente registrato nella tabella dei metadati personalizzata per un determinato utente.

Per gli utenti con una transazione elevata recente, la UDF restituisce il valore "Alert" come previsto.

POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
    "arguments": [
        "Mark"
    ]
}

HTTP/1.1 200 OK
{
  "result": {
    "returnValue": "Alert"
  },
  "status": "Succeeded"
}

Per gli utenti con un recente basso volume di transazioni, la UDF (Funzione Definita dall'Utente) restituisce invece il valore "Normale".

POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
    "arguments": [
        "John"
    ]
}

HTTP/1.1 200 OK
{
  "result": {
    "returnValue": "Normal"
  },
  "status": "Succeeded"
}

Per gli utenti che non dispongono di alcuna voce nella tabella personalizzata, la UDF genera un errore come definito nel codice 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"
}

Considerazioni

  • Gli hook delle transazioni sono attualmente supportati solo per l'API POST /app/transactions, quando si aggiunge una nuova voce al libro mastro.

  • Le UDF e gli hook vengono sempre eseguiti nella replica primaria del libro mastro, per garantire l'ordinamento delle transazioni e la coerenza assoluta.

  • L'esecuzione del codice UDF è sempre incapsulata in una singola transazione atomica. Se la logica JavaScript in una UDF viene completata senza eccezioni, tutte le operazioni all'interno della UDF vengono eseguite nel libro mastro. Se viene sollevata un'eccezione, viene eseguito il rollback di tutte le transazioni. Analogamente, i pre-hook e i post-hook vengono eseguiti nello stesso contesto dell'operazione di scrittura a cui sono associati. Se un pre-hook o un post-hook ha esito negativo, l'intera transazione viene interrotta e non viene aggiunta alcuna voce al libro mastro.

  • Per motivi di sicurezza, le funzioni definite dall'utente possono accedere solo alle tabelle delle applicazioni CCF e non possono accedere alle tabelle interne e di governance del ledger o ad altre tabelle integrate. Le tabelle del libro mastro in cui vengono scritte le voci (public:confidentialledger.logs per i registri pubblici e private:confidentialledger.logs per i registri privati) sono di sola lettura.

  • Il numero di pre-hook e post-hook che una singola transazione di scrittura può eseguire è limitato. Per i limiti correnti, vedere limiti di Azure Confidential Ledger.

  • L'opzione max_execution_time_ms di runtime limita il tempo di esecuzione di UDF e hook. Per i valori predefiniti e massimi, vedere Esecuzione di funzioni definite dall'utente. Se una funzione viene eseguita più a lungo del limite, l'operazione viene interrotta e viene restituito un errore.