Condividi tramite


Funzioni semplici definite dall'utente nel libro mastro riservato di Azure (anteprima)

Le funzioni definite dall'utente semplici (UDF) nel libro mastro riservato di Azure consentono di creare funzioni JavaScript personalizzate che possono essere eseguite all'interno del limite di attendibilità del libro mastro. Questa funzionalità è progettata per essere semplice e facile da usare, consentendo di estendere la funzionalità dell'API libro mastro senza la necessità di sviluppo di applicazioni complesse.

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 funzioni definite dall'utente, guardare il video seguente:

Importante

Le funzioni definite dall'utente sono attualmente in ANTEPRIMA nella versione 2024-12-09-previewAPI . È possibile richiedere l'accesso per questa anteprima tramite questo modulo di iscrizione. Vedere le condizioni per l'utilizzo supplementari per le anteprime di Microsoft Azure per termini legali aggiuntivi che si applicano a funzionalità di Azure in versione beta, in anteprima o in altro modo non ancora disponibili a livello generale.

Suggerimento

Per scenari più avanzati, ad esempio Role-Based controllo degli accessi in base al ruolo (RBAC) personalizzato o integrazione con carichi di lavoro riservati esterni, vedere Funzioni avanzate definite dall'utente nel libro mastro riservato di Azure.

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 funzioni definite dall'utente includono:

  • Calcoli e query personalizzati: eseguire funzioni autonome definite dall'utente per leggere o scrivere dati in qualsiasi tabella dell'applicazione di registro contabile secondo la logica aziendale.

  • Controlli di convalida e input dei dati: usare le funzioni definite dall'utente (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 funzioni definite dall'utente 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 funzioni definite dall'utente

Una funzione definita dall'utente del libro mastro riservato di Azure è un'entità archiviata nel libro mastro con un ID univoco e contiene il codice JavaScript eseguito quando viene chiamata la funzione definita dall'utente. 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 della funzione

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

Gli argomenti vengono sempre passati come un array 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 (funzione definita dall'utente) 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 . Un oggetto globale denominato ccf può essere usato per accedere a funzionalità e utilità specifiche fornite da Confidential Consortium Framework (CCF), ad esempio funzioni helper di crittografia, funzioni di accesso alle tabelle mastro e così via. L'API completa dell'oggetto ccf globale è documentata qui.

È 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 funzioni definite dall'utente vengono archiviate in modo sicuro nel registro e sono accessibili solo all'applicazione del registro.

Annotazioni

Le funzioni definite dall'utente semplici e le funzioni avanzate definite dall'utente si escludono a vicenda. Non è possibile creare o eseguire UDF semplici se sono presenti UDF avanzate, e viceversa. Per passare da una all'altra, seguire le istruzioni nella pagina di panoramica della UDF (funzione definita dall'utente).

Creare o aggiornare una funzione definita dall'utente

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

Importante

Per creare o aggiornare una funzione definita dall'utente, è necessario il ruolo di amministratore.

Ottenere un'UDF

GET /app/userDefinedFunctions/myFunction

Elenco UDF

GET /app/userDefinedFunctions

Eliminare una funzione definita dall'utente

DELETE /app/userDefinedFunctions/myFunction

Importante

Per eliminare una funzione definita dall'utente (UDF), è necessario il ruolo di amministratore.

Annotazioni

L'eliminazione di una funzione definita dall'utente rimuove solo l'entità dallo stato corrente del registro. Qualsiasi funzione definita dall'utente (UDF) eliminata viene sempre conservata nella cronologia del registro immutabile, come qualsiasi transazione di cui è stato eseguito il commit.

Esecuzione di funzioni definite dall'utente

Dopo la creazione, gli utenti di Azure Confidential Ledger possono eseguire una funzione definita dall'utente come funzione autonoma o come gancio 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 funzione definita dall'utente (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 (funzione definita dall'utente). 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 della funzione definita dall'utente. Sono disponibili le opzioni seguenti:

    • max_heap_bytes: dimensione massima dell'heap in unità di byte. Il valore predefinito è 10.485.760 (10 MB).

    • max_stack_bytes: dimensione massima dello stack in byte. Il valore predefinito è 1.048.576 (1 MB).

    • max_execution_time_ms: tempo di esecuzione massimo in millisecondi. Il valore predefinito è 1000 (1 secondo).

    • log_exception_details: valore booleano che specifica se registrare i dettagli dell'eccezione. Il valore predefinito è true.

    • return_exception_details: valore booleano che specifica se restituire i dettagli dell'eccezione nella risposta. Il valore predefinito è true.

Funzioni autonome

Una funzione definita dall'utente 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_heap_bytes": 5,
        "max_stack_bytes": 1024,
        "max_execution_time_ms": 5000,
        "log_exception_details": true,
        "return_exception_details": true
    }
}

La risposta indica il risultato dell'esecuzione dell'UDF (riuscita o fallita). Se la funzione definita dall'utente 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 FDU.

Ganci delle transazioni

Una funzione definita dall'utente 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 funzioni definite dall'utente 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_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
            } 
        } 
    }
  ] 
} 

È possibile specificare fino a 5 pre-hook e post-hook nel corpo della richiesta, con qualsiasi combinazione. Gli hook vengono sempre eseguiti nell'ordine in cui vengono forniti nel corpo della richiesta.

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 funzione definita dall'utente (UDF) può essere vista utilizzata come pre-hook e post-hook nella stessa richiesta e chiamata più volte.

Esempi

Questa sezione illustra alcuni esempi pratici di come usare le funzioni definite dall'utente in Azure Confidential Ledger. Per gli scenari di esempio seguenti, si presuppone l'uso del libro mastro riservato di Azure 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 garantire che il contenuto della transazione sia sempre un numero, è possibile creare una funzione definita dall'utente per controllare il contenuto dell'input. Il pre-hook seguente controlla se il valore del contenuto è un numero e genera un errore in caso contrario.

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 funzione definita dall'utente 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 funzione definita dall'utente è 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

Funzioni definite dall'utente (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

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

  • Le funzioni definite dall'utente 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 funzione definita dall'utente (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.

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

  • Il numero massimo di pre-hook e post-hook che possono essere registrati per una singola transazione di scrittura è 5.

  • L'esecuzione di funzioni definite dall'utente e hook è limitata a 5 secondi. Se l'esecuzione di una funzione richiede più di 5 secondi, l'operazione viene interrotta e viene restituito un errore.