ts/ledger: package management (#8115)

This PR adds coverage for the package management endpoints of the JSON
API.

CHANGELOG_BEGIN

- [JavaScript Client Libraries] The Ledger object (returned by
  `useLedger` through the React bindings) has three new methods covering
  the package management API: `listPackages` returns a list of all known
  packageIDs, `getPackage` returns the binary data of the corresponding
  DALF, and `uploadDarFile` takes binary data and uploads it to the
  ledger. Note that `uploadDarFile` requires admin access.

CHANGELOG_END
This commit is contained in:
Gary Verhaegen 2020-12-01 13:58:29 +01:00 committed by GitHub
parent 0df1a79ee3
commit 5114fadd1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 9 deletions

View File

@ -85,6 +85,7 @@ sh_test(
"$(location //language-support/ts/daml-types:npm_package)",
"$(location //language-support/ts/daml-ledger:npm_package)",
sdk_version,
"$(location //ledger/test-common:model-tests.dar)",
],
data = [
"//:java",
@ -93,6 +94,7 @@ sh_test(
"//ledger/sandbox-classic:sandbox-classic-binary_deploy.jar",
"//ledger-service/http-json:http-json-binary_deploy.jar",
":build-and-lint.dar",
"//ledger/test-common:model-tests.dar",
"//language-support/ts/daml-types:npm_package",
"//language-support/ts/daml-ledger:npm_package",
] + glob(

View File

@ -46,6 +46,7 @@ TS_DIR=$(dirname $PACKAGE_JSON)
DAML_TYPES=$(rlocation "$TEST_WORKSPACE/$8")
DAML_LEDGER=$(rlocation "$TEST_WORKSPACE/$9")
SDK_VERSION=${10}
UPLOAD_DAR=$(rlocation "$TEST_WORKSPACE/${11}")
TMP_DAML_TYPES=$TMP_DIR/daml-types
TMP_DAML_LEDGER=$TMP_DIR/daml-ledger
@ -69,4 +70,4 @@ $YARN run build
$YARN run lint
# Invoke 'yarn test'. Control is thereby passed to
# 'language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts'.
JAVA=$JAVA SANDBOX=$SANDBOX JSON_API=$JSON_API DAR=$DAR $YARN test
JAVA=$JAVA SANDBOX=$SANDBOX JSON_API=$JSON_API DAR=$DAR UPLOAD_DAR=$UPLOAD_DAR $YARN test

View File

@ -530,3 +530,41 @@ test('party API', async () => {
expect(_.sortBy(allPartiesAfter)).toEqual(_.sortBy(["Alice", "Bob", "Dave", newParty1.identifier, newParty2.identifier]));
});
test('package API', async () => {
// expect().toThrow does not seem to work with async thunk
const expectFail = async <T>(p: Promise<T>): Promise<void> => {
try {
await p;
expect(true).toBe(false);
} catch (exc) {
expect(exc.status).toBe(500);
expect(exc.errors.length).toBe(1);
}
};
const ledger = new Ledger({token: ALICE_TOKEN, httpBaseUrl: httpBaseUrl()});
const packagesBefore = await ledger.listPackages();
expect(packagesBefore).toEqual(expect.arrayContaining([buildAndLint.packageId]));
expect(packagesBefore.length > 1).toBe(true);
const nonSense = Uint8Array.from([1, 2, 3, 4]);
await expectFail(ledger.uploadDarFile(nonSense));
const upDar = await fs.readFile(getEnv('UPLOAD_DAR'));
// throws on error
await ledger.uploadDarFile(upDar);
const packagesAfter = await ledger.listPackages();
expect(packagesAfter).toEqual(expect.arrayContaining([buildAndLint.packageId]));
expect(packagesAfter.length > packagesBefore.length).toBe(true);
expect(packagesAfter).toEqual(expect.arrayContaining(packagesBefore));
await expectFail(ledger.getPackage("non-sense"));
const downSuc = await ledger.getPackage(buildAndLint.packageId);
expect(downSuc.byteLength > 0).toBe(true);
});

View File

@ -112,18 +112,34 @@ format of the JSON API. See the [JSON API docs] for details.
`getParties`
------------
For a given list of party identifiers, returns full information, or null if
For a given list of party identifiers, return full information, or null if
the party doesn't exist.
`listKnownParties`
------------------
Returns an array of PartyInfo for all parties on the ledger.
Return an array of PartyInfo for all parties on the ledger.
`allocateParty`
---------------
Allocates a new party.
Allocate a new party.
`listPackages`
--------------
Fetch a list of all known package IDs.
`getPackage`
------------
Given a package ID, fetch the binary data for the corresponding DALF.
`uploadDarFile`
---------------
Upload a given byte array as a DAR to the ledger. Note that this requires a
token with admin access.
## Source

View File

@ -24,6 +24,8 @@ const partyInfoDecoder: jtv.Decoder<PartyInfo> =
isLocal: jtv.boolean(),
});
export type PackageId = string;
const decode = <R>(decoder: jtv.Decoder<R>, data: unknown): R => {
return jtv.Result.withException(decoder.run(data));
}
@ -313,6 +315,24 @@ class Ledger {
this.reconnectThreshold = reconnectThreshold;
}
/**
* @internal
*/
private auth(): {[headers: string]: string} {
return {'Authorization': 'Bearer ' + this.token};
}
/**
* @internal
*/
private async throwOnError(r: Response): Promise<void> {
if (!r.ok) {
const json = await r.json();
console.log(json);
throw decode(decodeLedgerError, json);
}
}
/**
* @internal
*
@ -322,16 +342,13 @@ class Ledger {
const httpResponse = await fetch(this.httpBaseUrl + endpoint, {
body: JSON.stringify(payload),
headers: {
'Authorization': 'Bearer ' + this.token,
...this.auth(),
'Content-type': 'application/json'
},
method,
});
await this.throwOnError(httpResponse);
const json = await httpResponse.json();
if (!httpResponse.ok) {
console.log(json);
throw jtv.Result.withException(decodeLedgerError.run(json));
}
const ledgerResponse = jtv.Result.withException(decodeLedgerResponse.run(json));
if (ledgerResponse.warnings) {
console.warn(ledgerResponse.warnings);
@ -953,6 +970,51 @@ class Ledger {
return decode(partyInfoDecoder, json);
}
/**
* Fetch a list of all package IDs from the ledger.
*
* @returns List of package IDs.
*
*/
async listPackages(): Promise<PackageId[]> {
const json = await this.submit('v1/packages', undefined, 'get');
return decode(jtv.array(jtv.string()), json);
}
/**
* Fetch a binary package.
*
* @returns The content of the package as a raw ArrayBuffer.
*
*/
async getPackage(id: PackageId): Promise<ArrayBuffer> {
const httpResponse = await fetch(this.httpBaseUrl + 'v1/packages/' + id, {
headers: this.auth(),
method: 'get',
});
await this.throwOnError(httpResponse);
return await httpResponse.arrayBuffer();
}
/**
* Upload a binary archive. Note that this requires admin privileges.
*
* @returns No return value on success; throws on error.
*
*/
async uploadDarFile(abuf: ArrayBuffer): Promise<void> {
const httpResponse = await fetch(this.httpBaseUrl + 'v1/packages', {
body: abuf,
headers: {
...this.auth(),
'Content-type': 'application/octet-stream'
},
method: 'post',
});
await this.throwOnError(httpResponse);
return;
}
}
export default Ledger;