Feat/js action (#1437)

* Feat: js action

wip: Getting async js

feat: Have execute get action config

feat: Read + Write

chore: Add typing for globals

chore: Change the default path, include error on missing function, and add json File Read Write

chore: Change the default path, include error on missing function, and add json File Read Write

wip: Fix the unit test

wip: Fix the unit test

feat: module loading

* fix: Change the source + add input

* fix: single thread runtime

* fix: Smaller fixes

* Apply suggestions from code review

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>

* fix: pr

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
J M
2022-05-19 18:02:50 -06:00
committed by GitHub
parent 2b6e54da1e
commit f7b5fb55d7
21 changed files with 2486 additions and 75 deletions

View File

@@ -0,0 +1,7 @@
import {Effects, Config, ConfigRes, SetResult, Properties} from './types';
export function properties(effects: Effects): Properties | Promise<Properties>;
export function getConfig(effects: Effects): ConfigRes | Promise<ConfigRes>;
export function setConfig(effects: Effects, input: Config): SetResult | Promise<SetResult>;

View File

@@ -0,0 +1,593 @@
// @ts-check
export function properties() {
return "Anything here"
}
/**
*
* @param {import('./types').Effects} effects
* @returns {Promise<import('./types').ConfigRes>}
*/
export async function getConfig(effects) {
await effects.writeFile({
path: "./test.log",
toWrite: "This is a test",
volumeId: 'main',
});
await effects.createDir({
path: "./testing",
volumeId: 'main',});
await effects.writeFile({
path: "./testing/test2.log",
toWrite: "This is a test",
volumeId: 'main',
});
await effects.removeFile({
path: "./testing/test2.log",
volumeId: 'main',
})
await effects.removeDir({
path: "./testing",
volumeId: 'main',});
effects.debug(`Read results are ${await effects.readFile({
path: "./test.log",
volumeId: 'main',
})}`)
effects.trace('trace')
effects.debug('debug')
effects.warn('warn')
effects.error('error')
effects.info('info')
return {
spec: {
"control-tor-address": {
"name": "Control Tor Address",
"description": "The Tor address for the control interface.",
"type": "pointer",
"subtype": "package",
"package-id": "lnd",
"target": "tor-address",
"interface": "control"
},
"peer-tor-address": {
"name": "Peer Tor Address",
"description": "The Tor address for the peer interface.",
"type": "pointer",
"subtype": "package",
"package-id": "lnd",
"target": "tor-address",
"interface": "peer"
},
"watchtower-tor-address": {
"name": "Watchtower Tor Address",
"description": "The Tor address for the watchtower interface.",
"type": "pointer",
"subtype": "package",
"package-id": "lnd",
"target": "tor-address",
"interface": "watchtower"
},
"alias": {
"type": "string",
"name": "Alias",
"description": "The public, human-readable name of your Lightning node",
"nullable": true,
"pattern": ".{1,32}",
"pattern-description": "Must be at least 1 character and no more than 32 characters"
},
"color": {
"type": "string",
"name": "Color",
"description": "The public color dot of your Lightning node",
"nullable": false,
"pattern": "[0-9a-fA-F]{6}",
"pattern-description": "Must be a valid 6 digit hexadecimal RGB value. The first two digits are red, middle two are green, and final two are\nblue\n",
"default": {
"charset": "a-f,0-9",
"len": 6
}
},
"accept-keysend": {
"type": "boolean",
"name": "Accept Keysend",
"description": "Allow others to send payments directly to your public key through keysend instead of having to get a new invoice\n",
"default": false
},
"accept-amp": {
"type": "boolean",
"name": "Accept Spontaneous AMPs",
"description": "If enabled, spontaneous payments through AMP will be accepted. Payments to AMP\ninvoices will be accepted regardless of this setting.\n",
"default": false
},
"reject-htlc": {
"type": "boolean",
"name": "Reject Routing Requests",
"description": "If true, LND will not forward any HTLCs that are meant as onward payments. This option will still allow LND to send\nHTLCs and receive HTLCs but lnd won't be used as a hop.\n",
"default": false
},
"min-chan-size": {
"type": "number",
"name": "Minimum Channel Size",
"description": "The smallest channel size that we should accept. Incoming channels smaller than this will be rejected.\n",
"nullable": true,
"range": "[1,16777215]",
"integral": true,
"units": "satoshis"
},
"max-chan-size": {
"type": "number",
"name": "Maximum Channel Size",
"description": "The largest channel size that we should accept. Incoming channels larger than this will be rejected.\nFor non-Wumbo channels this limit remains 16777215 satoshis by default as specified in BOLT-0002. For wumbo\nchannels this limit is 1,000,000,000 satoshis (10 BTC). Set this config option explicitly to restrict your maximum\nchannel size to better align with your risk tolerance. Don't forget to enable Wumbo channels under 'Advanced,' if desired.\n",
"nullable": true,
"range": "[1,1000000000]",
"integral": true,
"units": "satoshis"
},
"tor": {
"type": "object",
"name": "Tor Config",
"spec": {
"use-tor-only": {
"type": "boolean",
"name": "Use Tor for all traffic",
"description": "Use the tor proxy even for connections that are reachable on clearnet. This will hide your node's public IP address, but will slow down your node's performance",
"default": false
},
"stream-isolation": {
"type": "boolean",
"name": "Stream Isolation",
"description": "Enable Tor stream isolation by randomizing user credentials for each connection. With this mode active, each connection will use a new circuit. This means that multiple applications (other than lnd) using Tor won't be mixed in with lnd's traffic.\nThis option may not be used when 'Use Tor for all traffic' is disabled, since direct connections compromise source IP privacy by default.",
"default": false
}
}
},
"bitcoind": {
"type": "union",
"name": "Bitcoin Core",
"description": "The Bitcoin Core node to connect to:\n - internal: The Bitcoin Core and Proxy services installed to your Embassy\n - external: An unpruned Bitcoin Core node running on a different device\n",
"tag": {
"id": "type",
"name": "Type",
"variant-names": {
"internal": "Internal (Bitcoin Core)",
"internal-proxy": "Internal (Bitcoin Proxy)",
"external": "External"
},
"description": "The Bitcoin Core node to connect to:\n - internal: The Bitcoin Core and Proxy services installed to your Embassy\n - external: An unpruned Bitcoin Core node running on a different device\n"
},
"default": "internal",
"variants": {
"internal": {
"user": {
"type": "pointer",
"name": "RPC Username",
"description": "The username for Bitcoin Core's RPC interface",
"subtype": "package",
"package-id": "bitcoind",
"target": "config",
"multi": false,
"selector": "$.rpc.username"
},
"password": {
"type": "pointer",
"name": "RPC Password",
"description": "The password for Bitcoin Core's RPC interface",
"subtype": "package",
"package-id": "bitcoind",
"target": "config",
"multi": false,
"selector": "$.rpc.password"
}
},
"internal-proxy": {
"user": {
"type": "pointer",
"name": "RPC Username",
"description": "The username for the RPC user allocated to lnd",
"subtype": "package",
"package-id": "btc-rpc-proxy",
"target": "config",
"multi": false,
"selector": "$.users[?(@.name == \"lnd\")].name"
},
"password": {
"type": "pointer",
"name": "RPC Password",
"description": "The password for the RPC user allocated to lnd",
"subtype": "package",
"package-id": "btc-rpc-proxy",
"target": "config",
"multi": false,
"selector": "$.users[?(@.name == \"lnd\")].password"
}
},
"external": {
"connection-settings": {
"type": "union",
"name": "Connection Settings",
"description": "Information to connect to an external unpruned Bitcoin Core node",
"tag": {
"id": "type",
"name": "Type",
"description": "- Manual: Raw information for finding a Bitcoin Core node\n- Quick Connect: A Quick Connect URL for a Bitcoin Core node\n",
"variant-names": {
"manual": "Manual",
"quick-connect": "Quick Connect"
}
},
"default": "quick-connect",
"variants": {
"manual": {
"host": {
"type": "string",
"name": "Public Address",
"description": "The public address of your Bitcoin Core server",
"nullable": false
},
"rpc-user": {
"type": "string",
"name": "RPC Username",
"description": "The username for the RPC user on your Bitcoin Core RPC server",
"nullable": false
},
"rpc-password": {
"type": "string",
"name": "RPC Password",
"description": "The password for the RPC user on your Bitcoin Core RPC server",
"nullable": false
},
"rpc-port": {
"type": "number",
"name": "RPC Port",
"description": "The port that your Bitcoin Core RPC server is bound to",
"nullable": false,
"range": "[0,65535]",
"integral": true,
"default": 8332
},
"zmq-block-port": {
"type": "number",
"name": "ZeroMQ Block Port",
"description": "The port that your Bitcoin Core ZeroMQ server is bound to for raw blocks",
"nullable": false,
"range": "[0,65535]",
"integral": true,
"default": 28332
},
"zmq-tx-port": {
"type": "number",
"name": "ZeroMQ Transaction Port",
"description": "The port that your Bitcoin Core ZeroMQ server is bound to for raw transactions",
"nullable": false,
"range": "[0,65535]",
"integral": true,
"default": 28333
}
},
"quick-connect": {
"quick-connect-url": {
"type": "string",
"name": "Quick Connect URL",
"description": "The Quick Connect URL for your Bitcoin Core RPC server\nNOTE: LND will not accept a .onion url for this option\n",
"nullable": false,
"pattern": "btcstandup://[^:]*:[^@]*@[a-zA-Z0-9.-]+:[0-9]+(/(\\?(label=.+)?)?)?",
"pattern-description": "Must be a valid Quick Connect URL. For help, check out https://github.com/BlockchainCommons/Gordian/blob/master/Docs/Quick-Connect-API.md"
},
"zmq-block-port": {
"type": "number",
"name": "ZeroMQ Block Port",
"description": "The port that your Bitcoin Core ZeroMQ server is bound to for raw blocks",
"nullable": false,
"range": "[0,65535]",
"integral": true,
"default": 28332
},
"zmq-tx-port": {
"type": "number",
"name": "ZeroMQ Transaction Port",
"description": "The port that your Bitcoin Core ZeroMQ server is bound to for raw transactions",
"nullable": false,
"range": "[0,65535]",
"integral": true,
"default": 28333
}
}
}
}
}
}
},
"autopilot": {
"type": "object",
"name": "Autopilot",
"description": "Autopilot Settings",
"spec": {
"enabled": {
"type": "boolean",
"name": "Enabled",
"description": "If the autopilot agent should be active or not. The autopilot agent will\nattempt to AUTOMATICALLY OPEN CHANNELS to put your node in an advantageous\nposition within the network graph. DO NOT ENABLE THIS IF YOU WANT TO MANAGE \nCHANNELS MANUALLY OR DO NOT UNDERSTAND IT.\n",
"default": false
},
"private": {
"type": "boolean",
"name": "Private",
"description": "Whether the channels created by the autopilot agent should be private or not.\nPrivate channels won't be announced to the network.\n",
"default": false
},
"maxchannels": {
"type": "number",
"name": "Maximum Channels",
"description": "The maximum number of channels that should be created.",
"nullable": false,
"range": "[1,*)",
"integral": true,
"default": 5
},
"allocation": {
"type": "number",
"name": "Allocation",
"description": "The fraction of total funds that should be committed to automatic channel\nestablishment. For example 60% means that 60% of the total funds available\nwithin the wallet should be used to automatically establish channels. The total\namount of attempted channels will still respect the \"Maximum Channels\" parameter.\n",
"nullable": false,
"range": "[0,100]",
"integral": false,
"default": 60,
"units": "%"
},
"min-channel-size": {
"type": "number",
"name": "Minimum Channel Size",
"description": "The smallest channel that the autopilot agent should create.",
"nullable": false,
"range": "[0,*)",
"integral": true,
"default": 20000,
"units": "satoshis"
},
"max-channel-size": {
"type": "number",
"name": "Maximum Channel Size",
"description": "The largest channel that the autopilot agent should create.",
"nullable": false,
"range": "[0,*)",
"integral": true,
"default": 16777215,
"units": "satoshis"
},
"advanced": {
"type": "object",
"name": "Advanced",
"description": "Advanced Options",
"spec": {
"min-confirmations": {
"type": "number",
"name": "Minimum Confirmations",
"description": "The minimum number of confirmations each of your inputs in funding transactions\ncreated by the autopilot agent must have.\n",
"nullable": false,
"range": "[0,*)",
"integral": true,
"default": 1,
"units": "blocks"
},
"confirmation-target": {
"type": "number",
"name": "Confirmation Target",
"description": "The confirmation target (in blocks) for channels opened by autopilot.",
"nullable": false,
"range": "[0,*)",
"integral": true,
"default": 1,
"units": "blocks"
}
}
}
}
},
"advanced": {
"type": "object",
"name": "Advanced",
"description": "Advanced Options",
"spec": {
"debug-level": {
"type": "enum",
"name": "Log Verbosity",
"values": [
"trace",
"debug",
"info",
"warn",
"error",
"critical"
],
"description": "Sets the level of log filtration. Trace is the most verbose, Critical is the least.\n",
"default": "info",
"value-names": {}
},
"db-bolt-no-freelist-sync": {
"type": "boolean",
"name": "Disallow Bolt DB Freelist Sync",
"description": "If true, prevents the database from syncing its freelist to disk.\n",
"default": false
},
"db-bolt-auto-compact": {
"type": "boolean",
"name": "Compact Database on Startup",
"description": "Performs database compaction on startup. This is necessary to keep disk usage down over time at the cost of\nhaving longer startup times.\n",
"default": true
},
"db-bolt-auto-compact-min-age": {
"type": "number",
"name": "Minimum Autocompaction Age for Bolt DB",
"description": "How long ago (in hours) the last compaction of a database file must be for it to be considered for auto\ncompaction again. Can be set to 0 to compact on every startup.\n",
"nullable": false,
"range": "[0, *)",
"integral": true,
"default": 168,
"units": "hours"
},
"db-bolt-db-timeout": {
"type": "number",
"name": "Bolt DB Timeout",
"description": "How long should LND try to open the database before giving up?",
"nullable": false,
"range": "[1, 86400]",
"integral": true,
"default": 60,
"units": "seconds"
},
"recovery-window": {
"type": "number",
"name": "Recovery Window",
"description": "Number of blocks in the past that LND should scan for unknown transactions",
"nullable": true,
"range": "[1,*)",
"integral": true,
"units": "blocks"
},
"payments-expiration-grace-period": {
"type": "number",
"name": "Payments Expiration Grace Period",
"description": "A period to wait before for closing channels with outgoing htlcs that have timed out and are a result of this\nnodes instead payment. In addition to our current block based deadline, is specified this grace period will\nalso be taken into account.\n",
"nullable": false,
"range": "[1,*)",
"integral": true,
"default": 30,
"units": "seconds"
},
"default-remote-max-htlcs": {
"type": "number",
"name": "Maximum Remote HTLCs",
"description": "The default max_htlc applied when opening or accepting channels. This value limits the number of concurrent\nHTLCs that the remote party can add to the commitment. The maximum possible value is 483.\n",
"nullable": false,
"range": "[1,483]",
"integral": true,
"default": 483,
"units": "htlcs"
},
"max-channel-fee-allocation": {
"type": "number",
"name": "Maximum Channel Fee Allocation",
"description": "The maximum percentage of total funds that can be allocated to a channel's commitment fee. This only applies for\nthe initiator of the channel.\n",
"nullable": false,
"range": "[0.1, 1]",
"integral": false,
"default": 0.5
},
"max-commit-fee-rate-anchors": {
"type": "number",
"name": "Maximum Commitment Fee for Anchor Channels",
"description": "The maximum fee rate in sat/vbyte that will be used for commitments of channels of the anchors type. Must be\nlarge enough to ensure transaction propagation.\n",
"nullable": false,
"range": "[1,*)",
"integral": true,
"default": 10
},
"protocol-wumbo-channels": {
"type": "boolean",
"name": "Enable Wumbo Channels",
"description": "If set, then lnd will create and accept requests for channels larger than 0.16 BTC\n",
"default": false
},
"protocol-no-anchors": {
"type": "boolean",
"name": "Disable Anchor Channels",
"description": "Set to disable support for anchor commitments. Anchor channels allow you to determine your fees at close time by\nusing a Child Pays For Parent transaction.\n",
"default": false
},
"protocol-disable-script-enforced-lease": {
"type": "boolean",
"name": "Disable Script Enforced Channel Leases",
"description": "Set to disable support for script enforced lease channel commitments. If not set, lnd will accept these channels by default if the remote channel party proposes them. Note that lnd will require 1 UTXO to be reserved for this channel type if it is enabled.\nNote: This may cause you to be unable to close a channel and your wallets may not understand why",
"default": false
},
"gc-canceled-invoices-on-startup": {
"type": "boolean",
"name": "Cleanup Canceled Invoices on Startup",
"description": "If true, LND will attempt to garbage collect canceled invoices upon start.\n",
"default": false
},
"bitcoin": {
"type": "object",
"name": "Bitcoin Channel Configuration",
"description": "Configuration options for lightning network channel management operating over the Bitcoin network",
"spec": {
"default-channel-confirmations": {
"type": "number",
"name": "Default Channel Confirmations",
"description": "The default number of confirmations a channel must have before it's considered\nopen. LND will require any incoming channel requests to wait this many\nconfirmations before it considers the channel active.\n",
"nullable": false,
"range": "[1,6]",
"integral": true,
"default": 3,
"units": "blocks"
},
"min-htlc": {
"type": "number",
"name": "Minimum Incoming HTLC Size",
"description": "The smallest HTLC LND will to accept on your channels, in millisatoshis.\n",
"nullable": false,
"range": "[1,*)",
"integral": true,
"default": 1,
"units": "millisatoshis"
},
"min-htlc-out": {
"type": "number",
"name": "Minimum Outgoing HTLC Size",
"description": "The smallest HTLC LND will send out on your channels, in millisatoshis.\n",
"nullable": false,
"range": "[1,*)",
"integral": true,
"default": 1000,
"units": "millisatoshis"
},
"base-fee": {
"type": "number",
"name": "Routing Base Fee",
"description": "The base fee in millisatoshi you will charge for forwarding payments on your\nchannels.\n",
"nullable": false,
"range": "[0,*)",
"integral": true,
"default": 1000,
"units": "millisatoshi"
},
"fee-rate": {
"type": "number",
"name": "Routing Fee Rate",
"description": "The fee rate used when forwarding payments on your channels. The total fee\ncharged is the Base Fee + (amount * Fee Rate / 1000000), where amount is the\nforwarded amount. Measured in sats per million\n",
"nullable": false,
"range": "[1,1000000)",
"integral": true,
"default": 1,
"units": "sats per million"
},
"time-lock-delta": {
"type": "number",
"name": "Time Lock Delta",
"description": "The CLTV delta we will subtract from a forwarded HTLC's timelock value.",
"nullable": false,
"range": "[6, 144]",
"integral": true,
"default": 40,
"units": "blocks"
}
}
}
}
}
}
}
}
/**
* @param {import ("./types").Effects} effects
* @param {import("./types").Config} input
* @returns {Promise<import("./types").SetResult>}
*/
export async function setConfig(effects, input) {
return {
"depends-on": {}
}
}

View File

@@ -0,0 +1,191 @@
export type Effects = {
writeFile(input: {path: string, volumeId: string, toWrite: string}): Promise<void>,
readFile(input: {volumeId: string,path: string}): Promise<string>,
createDir(input: {volumeId: string,path: string}): Promise<string>,
removeDir(input: {volumeId: string,path: string}): Promise<string>,
removeFile(input: {volumeId: string,path: string}): Promise<void>,
writeJsonFile(input: {volumeId: string,path: string, toWrite: object}): void,
readJsonFile(input:{volumeId: string,path: string}): object,
trace(whatToPrin: string),
warn(whatToPrin: string),
error(whatToPrin: string),
debug(whatToPrin: string),
info(whatToPrin: string),
is_sandboxed(): boolean,
}
export type ActionResult = {
version: "0",
message: string,
value?: string,
copyable: boolean,
qr: boolean,
}
export type ConfigRes = {
config?: Config,
spec: ConfigSpec,
}
export type Config = {
[value: string]: any
}
export type ConfigSpec = {
[value: string]: ValueSpecAny
}
export type WithDefault<T, Default> = T & {
default?: Default
}
export type WithDescription<T> = T & {
description?: String,
name: string,
warning?: string,
}
export type ListSpec<T> = {
spec: T,
range: string
}
export type Tag<T extends string, V> = V & {
type: T
}
export type Subtype<T extends string, V> = V & {
subtype: T
}
export type Target<T extends string, V> = V & {
"target": T
}
export type UniqueBy =
|{
any: UniqueBy[],
}
| {
all: UniqueBy[]
}
| string
| null
export type WithNullable<T> = T & {
nullable: boolean
}
export type DefaultString = String | {
charset?: string,
len: number
}
export type ValueSpecString= ({} | {
pattern: string,
'pattern-description': string
}) & {
copyable?: boolean,
masked?: boolean,
placeholder?: string
}
export type ValueSpecNumber = {
range?: string,
integral?: boolean,
units?: string,
placeholder?: number,
}
export type ValueSpecBoolean = {}
export type ValueSpecAny =
| Tag<'boolean', WithDescription<WithDefault<ValueSpecBoolean, boolean>>>
| Tag<'string', WithDescription<WithDefault<WithNullable<ValueSpecString>, DefaultString>>>
| Tag<'number', WithDescription<WithDefault<WithNullable<ValueSpecNumber>, number>>>
| Tag<'enum', WithDescription<WithDefault<{
values: string[],
"value-names": {
[key: string]: string
}
}, string>>>
| Tag<'list', ValueSpecList>
| Tag<'object', WithDescription<WithDefault<ValueSpecObject, Config>>>
| Tag<'union', WithDescription<WithDefault<ValueSpecUnion, string>>>
| Tag<'pointer', WithDescription<
| Subtype<'package',
| Target<'tor-key', {
'package-id': string
interface: string
}>
| Target<'tor-address', {
'package-id': string,
interface: string
} >
| Target<'lan-address',{
'package-id': string,
interface: string
} >
| Target<'config', {
'package-id': string,
selector: string,
multi: boolean
}>
>
| Subtype<'system', {}>
>>
export type ValueSpecUnion = {
tag: {
id: string,
name: string,
description?: string,
"variant-names": {
[key: string]: string,
}
},
variants: {
[key: string]: ConfigSpec
},
"display-as"?: string,
"unique-by"?: UniqueBy
}
export type ValueSpecObject = {
spec : ConfigSpec,
'display-as'?: string,
"unique-by"?: UniqueBy
}
export type ValueSpecList =
| Subtype<'boolean', WithDescription<WithDefault<ListSpec<ValueSpecBoolean>, boolean>>>
| Subtype<'string', WithDescription<WithDefault<ListSpec<ValueSpecString>, string>>>
| Subtype<'number', WithDescription<WithDefault<ListSpec<ValueSpecNumber>, number>>>
| Subtype<'enum', WithDescription<WithDefault<{
values: string[],
"value-names": {
[key: string]: string
}
}, string>>>
export type SetResult = {
signal?: string,
'depends-on': {
[packageId: string]: string[]
}
}
export type PackagePropertiesV2 = {
[name: string]: PackagePropertyObject | PackagePropertyString
}
export type PackagePropertyString = {
type: 'string',
description?: string,
value: string,
copyable?: boolean,
qr?: boolean,
masked?: boolean,
}
export type PackagePropertyObject = {
value: PackagePropertiesV2;
type: "object";
description: string;
}
export type Properties = {
version: 2,
data: PackagePropertiesV2
}