Enkla användardefinierade funktioner i azure confidential ledger (förhandsversion)

Med enkla användardefinierade funktioner (UDF:er) i azure confidential ledger kan du skapa anpassade JavaScript-funktioner som kan köras innanför transaktionsregistrets förtroendegräns. Den här funktionen är utformad för att vara enkel och enkel att använda, så att du kan utöka funktionerna i transaktionsregistrets API utan att behöva utveckla komplexa program.

Med hjälp av det inbyggda JavaScript-API:et kan du köra anpassad kod för att uppnå olika uppgifter, till exempel anpassade frågor och beräkningar, villkorliga kontroller, uppgifter efter bearbetning med mera. Den här funktionen är lämplig för scenarier där du behöver en direkt integrering med det befintliga transaktionsregister-API:et eller köra enkel anpassad logik i en konfidentiell miljö.

En snabb översikt och demonstration av UDF:er finns i följande video:

Viktigt!

Användardefinierade funktioner är för närvarande i förhandsversion under API-version 2024-12-09-preview. Du kan begära åtkomst för den här förhandsversionen via det här registreringsformuläret. Se kompletterande användningsvillkor för Microsoft Azure Previews för juridiska villkor som gäller för Azure-funktioner som är i betaversion, förhandsversion eller på annat sätt ännu inte har släppts i allmän tillgänglighet.

Tips/Råd

Mer avancerade scenarier, till exempel anpassad Role-Based Åtkomstkontroll (RBAC) eller integrering med externa konfidentiella arbetsbelastningar, finns i avancerade användardefinierade funktioner i Azure Confidential Ledger.

Användningsfall

Med azure confidential ledger-UDF:er kan du utöka funktionerna i transaktionsregistret genom att köra anpassad logik. Några vanliga användningsfall för UDF:er är:

  • Anpassade beräkningar och frågor: kör fristående UDF:er för att läsa eller skriva data i en transaktionsregisterapptabell enligt din affärslogik.

  • Dataverifierings- och indatakontroller: Använd UDF:er som pre-hooks för att köra förbearbetningsåtgärder innan en transaktionsregisterpost skrivs till transaktionsregistret, till exempel för att sanera indata eller söka efter förhandsvillkor.

  • Databerikning och smarta kontrakt: Använd UDF:er som post-hooks för att köra åtgärder efter bearbetning när en transaktionsregisterpost har skrivits, till exempel för att lägga till anpassade metadata i transaktionsregistret eller utlösa arbetsflöden efter skrivning.

Skriva UDF:er

En Azure Confidential Ledger UDF är en entitet som lagras i huvudboken med ett unikt ID och innehåller JavaScript-koden som körs när UDF:en anropas. I det här avsnittet beskrivs hur du skriver UDF-kod och använder JavaScript-API:et för att uppnå olika uppgifter.

Funktionsstruktur

Koden för en UDF kräver en exporterad funktion som är startpunkten för skriptet vid tidpunkten för körningen. En grundläggande UDF-kodmall ser ut så här:

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

Anmärkning

Namnet på den exporterade entrypointfunktionen som anropas under körningen kan ändras med exportedFunctionName-argumentet när UDF körs. Om det inte anges är mainstandardnamnet .

Anmärkning

Lambda-funktioner stöds, men de kräver att det exporterade funktionsnamnet uttryckligen definieras och matchar namnet på funktionen entrypoint. Till exempel:

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

Funktionsargument

Du kan ange valfria körningsargument som kan användas av UDF. Argumentets värden kan skickas vid körning av UDF med hjälp av parametern arguments.

Argumenten skickas alltid som en matris med strängar. Det är användarens ansvar att se till att argumenten som anges i UDF-koden matchar argumenten som skickades när UDF kördes. Användaren bör också se till att argumenten parsas korrekt till den förväntade datatypen vid exekvering.

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

JavaScript-API

JavaScript-koden för en UDF körs i en sandbox-miljö som tillhandahåller en begränsad uppsättning API:er.

Alla globala JavaScript-standardfunktioner, objekt och värden kan användas. Ett globalt objekt med namnet ccf kan användas för att komma åt specifika funktioner och verktyg som tillhandahålls av Confidential Consortium Framework (CCF) ( till exempel kryptografihjälpfunktioner, transaktionsregistertabeller osv.). Det fullständiga API:et för det ccf globala objektet dokumenteras här.

Du kan också komma åt kontextuell information om den aktuella begäran med hjälp av det context globala objektet. Det här objektet ger åtkomst till begärandemetadata som har sitt ursprung i funktionskörningen (context.request) och användar-ID:t för funktionsanroparen (context.userId). För transaktionskrokar läggs samlings-ID:t och transaktionsinnehållet som är kopplat till skrivåtgärden också till i context objektet (context.collectionId respektive context.contents ).

Följande kodfragment visar några grundläggande exempel på användningen av JavaScript-API:et:

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

Tips/Råd

Mer information om hur transaktionsregisterkartor kan användas för att lagra och hämta data finns i CCF-dokumentationen för Key-Value Store-API:et.

Anmärkning

Import av moduler stöds inte i UDF:er. JavaScript-koden måste vara fristående och kan inte förlita sig på externa bibliotek eller moduler. Webb-API:er stöds inte heller för tillfället.

Hantera UDF:er

Azures konfidentiella transaktionsregisterprogram tillhandahåller ett dedikerat CRUD-API för att skapa, läsa, uppdatera och ta bort UDF-entiteter. UDFs lagras säkert i transaktionsregistret och är endast tillgängliga för transaktionsregisterapplikationen.

Anmärkning

Enkla användardefinierade funktioner och avancerade användardefinierade funktioner är ömsesidigt uteslutande funktioner. Du kan inte skapa eller köra enkla UDF:er om avancerade UDF:er har definierats och vice versa. Om du vill växla mellan de två följer du anvisningarna på UDF-översiktssidan.

Skapa eller uppdatera en UDF

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

Viktigt!

Administratörsroll krävs för att skapa eller uppdatera en UDF.

Hämta en UDF

GET /app/userDefinedFunctions/myFunction

Lista UDF:er

GET /app/userDefinedFunctions

Ta bort en UDF

DELETE /app/userDefinedFunctions/myFunction

Viktigt!

Administratörsrollen krävs för att ta bort en UDF.

Anmärkning

Om du tar bort en UDF tas bara entiteten bort från det aktuella tillståndet för transaktionsregistret. Alla borttagna UDF behålls alltid i den oföränderliga transaktionsregistrets historik (som en allokerad transaktion).

Köra UDF:er

När en Azure Confidential Ledger har skapats kan användarna köra en UDF antingen som en fristående funktion eller som en transaktionsfunktion kopplad till en skrivåtgärd. Varje UDF-körning körs i en separat körningsmiljö och sandbox-miljö, vilket innebär att UDF-körningen är isolerad från andra UDF-filer eller andra transaktionsregisteråtgärder.

UDF-körningen kan styras med valfria egenskaper som kan anges i begärandetexten. De egenskaper som stöds för närvarande är:

  • arguments: en matris med strängar som representerar argumenten som ska skickas till UDF. Argumenten skickas i samma ordning som de definieras i UDF-koden. Standardvärdet är en tom matris.

  • exportedFunctionName: namnet på den exporterade funktion som ska anropas under körningen. Om det inte anges är mainstandardvärdet .

  • runtimeOptions: ett objekt som anger alternativ för körning av UDF. Följande alternativ är tillgängliga:

    • max_heap_bytes: den maximala heapstorleken i byte. Standardvärdet är 10 485 760 (10 MB).

    • max_stack_bytes: den maximala stackstorleken i byte. Standardvärdet är 1 048 576 (1 MB).

    • max_execution_time_ms: den maximala exekveringstiden i millisekunder. Standardvärdet är 1 000 (1 sekund).

    • log_exception_details: ett booleskt värde som anger om undantagsinformation ska loggas. Standardvärdet är true.

    • return_exception_details: ett booleskt värde som anger om undantagsinformation ska returneras i svaret. Standardvärdet är true.

Fristående funktioner

En UDF kan köras direkt med hjälp av API:et POST /app/userDefinedFunctions/{functionId}:execute .

POST /app/userDefinedFunctions/myFunction:execute
{}

Begärandetexten kan användas för att ange valfria exekveringsparametrar, till exempel funktionsargument och JavaScript-körtidsegenskaper.

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

Svaret anger resultatet av UDF-körningen (lyckades eller misslyckades). Om UDF lyckades innehåller svaret det returnerade värdet från funktionen i strängformat (om något).

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

Om UDF har misslyckats innehåller svaret felmeddelandet med stacktracens detaljer.

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

Viktigt!

Deltagarrollen krävs för att köra en användardefinierad funktion (UDF).

Transaktionshooks

En UDF kan alternativt köras som en hook före (pre-hook) eller efter (post-hook) en post skrivs till transaktionsregistret som en del av loggskrivnings-API:t (POST /app/transactions). Krokar körs i samma kontext som skrivåtgärden. Det innebär att alla data som skrivs till transaktionsregistret av krokarna automatiskt inkluderas i samma skrivtransaktion.

Begärandetexten i skrivbegäran kan användas för att specificera vilka UDF-ID:n som ska köras som förkrokar respektive efterkrokar.

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

Viktigt!

Krokar måste uttryckligen definieras i begärandetexten för skrivåtgärden. I allmänhet kan UDF:er inte köras automatiskt för varje skrivåtgärd när de har skapats.

För varje krok är det möjligt att ange valfria egenskaper för exekvering. Till exempel:

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

Du kan ange upp till 5 pre-hooks och post-hooks i begärandetexten, med valfri kombination. Krokarna körs alltid i den ordning de anges i begärandetexten.

Om en pre-hook eller post-hook misslyckas, avbryts hela transaktionen. I så fall innehåller svaret felmeddelandet med orsaken till felet:

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

Anmärkning

Även om flera krokar lyckas kan transaktionen fortfarande misslyckas om någon av de definierade pre-hooks eller post-hooks inte körs korrekt till slutförande.

Tips/Råd

En UDF kan återanvändas som både en förhook och en efterhook i samma begäran och anropas flera gånger.

Exempel

I det här avsnittet går vi igenom några praktiska exempel på hur du använder UDF:er i Azure Confidential Ledger. I följande exempelscenarier förutsätter vi att du använder azure confidential ledger för att lagra banktransaktioner för olika bankanvändare.

Kontext

För att lagra en banktransaktion för en användare kan det befintliga transaktionsregisterskrivnings-API:et användas: värdet för transaktionen är innehållet i transaktionsregistrets post och användar-ID:t kan vara samlingen eller nyckeln, där innehållet skrivs under.

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

HTTP/1.1 200 OK

Eftersom det inte finns någon validering av indatainnehållet går det att skriva ett icke-numeriskt värde som innehåll. Den här begäran lyckas till exempel även om innehållsvärdet inte är ett tal:

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

HTTP/1.1 200 OK

Förkrokar för dataverifiering

För att säkerställa att transaktionsinnehållet alltid är ett tal kan en UDF skapas för att kontrollera indatainnehållet. Följande pre-hook kontrollerar om innehållsvärdet är ett tal och genererar ett fel om inte.

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

Med hjälp av förkroken i skrivbegäran är det möjligt att framtvinga att indata motsvarar det förväntade formatet. Den tidigare begäran misslyckas nu som förväntat:

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

Giltiga begäranden som innehåller numeriska värden skulle i stället lyckas som förväntat:

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

HTTP/1.1 200 OK

Post-hooks för databerikning

När användarna utför nya banktransaktioner vill vi registrera när en transaktion är högre än ett visst tröskelvärde av granskningsskäl. En post-hook kan användas för att skriva anpassade metadata i en transaktionsregister efter en skrivåtgärd för att ange om transaktionen var högre än ett visst tröskelvärde.

Du kan till exempel skapa en UDF för att kontrollera transaktionsvärdet och skriva ett dummymeddelande ("Avisering" för höga värden, annars "Normal") under användarens inmatning i en anpassad huvudbokstabell (payment_metadata) om värdet är högre än 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

När UDF har skapats kan post-hook användas i nya skrivbegäranden:

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

Fristående UDF:er för anpassade frågor

Om du vill kontrollera de senaste värdena som skrivits till den anpassade tabellen payment_metadata med hjälp av post-hook kan du skapa en UDF för att läsa värdena från tabellen med ett indataanvändar-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

Genom att köra UDF direkt går det att kontrollera det senaste värdet som registrerats i tabellen med anpassade metadata för en viss användare.

För användare med en nyligen hög transaktion returnerar UDF värdet "Avisering" som förväntat.

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

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

För användare med en nyligen låg transaktion returnerar UDF värdet "Normal" i stället.

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

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

För användare som inte har någon post i den anpassade tabellen genererar UDF ett fel enligt definitionen i UDF-koden.

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

Överväganden

  • Transaktionskrokar stöds för närvarande endast för API:et POST /app/transactions när du lägger till en ny post i transaktionsregistret.

  • UDF:er och hooks körs alltid på den primära kopian av huvudboken för att säkerställa ordningen av transaktioner och stark konsistens.

  • UDF-kodkörningen är alltid omsluten i en enda atomisk transaktion. Om JavaScript-logiken i en UDF slutförs utan några undantag, bekräftas alla åtgärder i UDF i transaktionsregistret. Om ett undantag utlöses återställs hela transaktionerna. På samma sätt körs pre-hooks och post-hooks i samma sammanhang som den skrivåtgärd de är registrerade för. Om en pre-hook eller en post-hook misslyckas avbryts hela transaktionen och ingen post läggs till i transaktionsregistret.

  • UDF:er kan bara komma åt CCF-programtabeller och kan inte komma åt transaktionsregistrets interna tabeller och styrningstabeller eller andra inbyggda tabeller av säkerhetsskäl. De transaktionsregistertabeller där poster skrivs till (public:confidentialledger.logs för offentliga transaktionsregister och private:confidentialledger.logs för privata transaktionsregister) är skrivskyddade.

  • Det maximala antalet pre-hooks och post-hooks som kan registreras för en enda skrivtransaktion är 5.

  • UDF- och hook-exekvering är begränsade till 5 sekunder. Om det tar längre tid än 5 sekunder att köra en funktion avbryts åtgärden och ett fel returneras.