microsoft / CCF

Confidential Consortium Framework
https://microsoft.github.io/CCF/
Apache License 2.0
779 stars 210 forks source link

FrontendNotOpen when call sample javascript endpoint #3559

Closed DavidZhongSyd closed 2 years ago

DavidZhongSyd commented 2 years ago

Describe the bug Call the sample javascript endpoint, it return error with message "FrontendNotOpen".

To Reproduce

# copy from https://microsoft.github.io/CCF/main/build_apps/js_app_bundle.html
root@3af2a582a0d9:~/CCF/python/utils# cat set_js_app.json
{
  "actions": [
    {
      "name": "set_js_app",
      "args": {
        "bundle": {
          "metadata": {
            "endpoints": {
              "/compute": {
                "post": {
                  "js_module": "math.js",
                  "js_function": "compute",
                  "forwarding_required": "never",
                  "authn_policies": [
                    "user_cert"
                  ],
                  "mode": "readonly",
                  "openapi": {
                    "requestBody": {
                      "required": true,
                      "content": {
                        "application/json": {
                          "schema": {
                            "properties": {
                              "op": {
                                "type": "string",
                                "enum": [
                                  "add",
                                  "sub",
                                  "mul"
                                ]
                              },
                              "left": {
                                "type": "number"
                              },
                              "right": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "op",
                              "left",
                              "right"
                            ],
                            "type": "object",
                            "additionalProperties": false
                          }
                        }
                      }
                    },
                    "responses": {
                      "200": {
                        "description": "Compute result",
                        "content": {
                          "application/json": {
                            "schema": {
                              "properties": {
                                "result": {
                                  "type": "number"
                                }
                              },
                              "required": [
                                "result"
                              ],
                              "type": "object",
                              "additionalProperties": false
                            }
                          }
                        }
                      },
                      "400": {
                        "description": "Client-side error",
                        "content": {
                          "application/json": {
                            "schema": {
                              "properties": {
                                "error": {
                                  "description": "Error message",
                                  "type": "string"
                                }
                              },
                              "required": [
                                "error"
                              ],
                              "type": "object",
                              "additionalProperties": false
                            }
                          }
                        }
                      }
                    }
                  }
                }
              },
              "/compute2/{op}/{left}/{right}": {
                "get": {
                  "js_module": "math.js",
                  "js_function": "compute2",
                  "forwarding_required": "never",
                  "authn_policies": [
                    "user_cert"
                  ],
                  "mode": "readonly",
                  "openapi": {
                    "parameters": [
                      {
                        "name": "op",
                        "in": "path",
                        "required": true,
                        "schema": {
                          "type": "string",
                          "enum": [
                            "add",
                            "sub",
                            "mul"
                          ]
                        }
                      },
                      {
                        "name": "left",
                        "in": "path",
                        "required": true,
                        "schema": {
                          "type": "number"
                        }
                      },
                      {
                        "name": "right",
                        "in": "path",
                        "required": true,
                        "schema": {
                          "type": "number"
                        }
                      }
                    ],
                    "responses": {
                      "default": {
                        "description": "Default response"
                      }
                    }
                  }
                }
              }
            }
          },
          "modules": [
            {
              "name": "math.js",
              "module": "function compute_impl(op, left, right) {\n  let result;\n  if (op == \"add\") result = left + right;\n  else if (op == \"sub\") result = left - right;\n  else if (op == \"mul\") result = left * right;\n  else {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"unknown op\",\n      },\n    };\n  }\n\n  return {\n    body: {\n      result: result,\n    },\n  };\n}\n\nexport function compute(request) {\n  const body = request.body.json();\n\n  if (typeof body.left != \"number\" || typeof body.right != \"number\") {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"invalid operand type\",\n      },\n    };\n  }\n\n  return compute_impl(body.op, body.left, body.right);\n}\n\nexport function compute2(request) {\n  const params = request.params;\n\n  // Type of params is always string. Try to parse as float\n  let left = parseFloat(params.left);\n  if (isNaN(left)) {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"left operand is not a parseable number\",\n      },\n    };\n  }\n\n  let right = parseFloat(params.right);\n  if (isNaN(right)) {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"right operand is not a parseable number\",\n      },\n    };\n  }\n\n  return compute_impl(params.op, left, right);\n}\n"
            }
          ]
        },
        "disable_bytecode_cache": false
      }
    }
  ]
}
# raise proposal to deploy the bundle
root@3af2a582a0d9:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/proposals --cacert networkcert.pem --signing-cert member0_cert.pem --signing-key member0_privk.pem --data-binary @set_js_app.json -H "content-type: application/json"
{"ballot_count":0,"proposal_id":"d93b3163db098114476e9e9ef88b0ae1394dc2dcfb7c4796610a1bf2e5f1ff26","proposer_id":"674897fc7be70a91b0a3dfedcd0698cbcb700f8e7210c12325c6e8a168d7277f","state":"Open"}
# vote to accept the proposal
root@3af2a582a0d9:~/CCF/python/utils# cat vote_accept.json
{
  "ballot": "export function vote (proposal, proposerId) { return true }"
}

root@3af2a582a0d9:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/proposals/15c606bfdf80200e7ce1d030987d0bb5cb8511e969428050fc220f402625cc4d/ballots --cacert networkcert.pem --signing-key member0_privk.pem --signing-cert member0_cert.pem --data-binary @vote_accept.json -H "content-type: application/json"
{"ballot_count":1,"proposal_id":"15c606bfdf80200e7ce1d030987d0bb5cb8511e969428050fc220f402625cc4d","proposer_id":"674897fc7be70a91b0a3dfedcd0698cbcb700f8e7210c12325c6e8a168d7277f","state":"Accepted","votes":{"674897fc7be70a91b0a3dfedcd0698cbcb700f8e7210c12325c6e8a168d7277f":true}}
# call endpoint
root@3af2a582a0d9:~/CCF/python/utils# curl https://172.17.0.2:8080/app/compute2/add/3/5 -X GET --cacert networkcert.pem --cert user0_cert.pem --key user0_privk.pem
{"error":{"code":"FrontendNotOpen","message":"Frontend is not open."}}

root@3af2a582a0d9:~/CCF/python/utils# curl https://172.17.0.2:8080/app/compute2/add/3/5 -X GET --cacert networkcert.pem --cert member0_cert.pem --key member0_privk.pem
{"error":{"code":"FrontendNotOpen","message":"Frontend is not open."}}

Expected behavior Return computed value 8

Environment information ccfciteam/ccf-app-ci:2.0.0-dev7 git clone --branch ccf-2.0.0-dev7 https://github.com/microsoft/CCF.git

Additional context Add any other context about the problem here.

eddyashton commented 2 years ago

@DavidZhongSyd This error is returned for any requests under /app until the network is opened, as mentioned here and described in more detail in these steps.

DavidZhongSyd commented 2 years ago

Thanks @eddyashton

After I opened the network and tried to call the endpoint compute2/add/3/5 again, it reports "Unknown path: /compute2/add/3/5."

May I ask what is the correct path please? Thanks.

$ cat transition_service_to_open.json
{
    "actions": [
        {
            "name": "transition_service_to_open",
            "args": null
        }
    ]
}

# Raise Open the network proposal
root@3af2a582a0d9:~/CCF/python/utils# ./scurl.sh https://172.17.0.4:8080/gov/proposals --cacert networkcert.pem --signing-key member0_privk.pem --signing-cert member0_cert.pem --data-binary @transition_service_to_open.json -H "content-type: application/json"
{"ballot_count":0,"proposal_id":"ec732653edc895dfc6a50b82d4e19d93b5de7054eb5b7853e9462242182fee38","proposer_id":"674897fc7be70a91b0a3dfedcd0698cbcb700f8e7210c12325c6e8a168d7277f","state":"Open"}

# vote to accept the proposal
$ cat vote_accept.json
{
  "ballot": "export function vote (proposal, proposerId) { return true }"
}

root@3af2a582a0d9:~/CCF/python/utils# ./scurl.sh https://172.17.0.4:8080/gov/proposals/ec732653edc895dfc6a50b82d4e19d93b5de7054eb5b7853e9462242182fee38/ballots --cacert networkcert.pem --signing-key member0_privk.pem --signing-cert member0_cert.pem --data-binary @vote_accept.json -H "content-type: application/json"
{"ballot_count":1,"proposal_id":"ec732653edc895dfc6a50b82d4e19d93b5de7054eb5b7853e9462242182fee38","proposer_id":"674897fc7be70a91b0a3dfedcd0698cbcb700f8e7210c12325c6e8a168d7277f","state":"Accepted","votes":{"674897fc7be70a91b0a3dfedcd0698cbcb700f8e7210c12325c6e8a168d7277f":true}}

Call compute2/add/3/5

root@3af2a582a0d9:~/CCF/python/utils# curl https://172.17.0.4:8080/app/compute2/add/3/5 -X GET --cacert networkcert.pem --cert member0_cert.pem --key member0_privk.pem
{"error":{"code":"ResourceNotFound","message":"Unknown path: /compute2/add/3/5."}}
eddyashton commented 2 years ago

That's odd, the endpoint should be available once the set_js_app proposal has been accepted. Can you call /app/api, which will return an OpenAPI document describing the available endpoints, and see if any of the endpoints defined in app.json (ie - /app/compute*`) are listed?

davidzhongsydney commented 2 years ago

Hi @eddyashton, after I run curl https://172.17.0.2:8080/app/api -X GET --cacert service_cert.pem --cert member0_cert.pem --key member0_privk.pem. I did not see the /app/compute2 in the output list.

Maybe I did something wrong. I firstly run /opt/ccf/bin/cchost.virtual --config /root/CCF/samples/apps/logging/build/start_config.json to start the /root/CCF/samples/apps/logging/build/liblogging.virtual.so, then raise the proposal to deploy the set_js_app.json. I am not sure if it is correct. If not, may I ask what is the correct way of deploy set_js_app.json.

Below are all the steps I have done. Thanks.

docker run -it ccfciteam/ccf-app-ci:2.0.0-rc0

apt update
apt upgrade

root@ef5e5f521395:/# cd ~
root@ef5e5f521395:~# git clone --branch ccf-2.0.0-rc0 https://github.com/microsoft/CCF.git

root@ef5e5f521395:~/CCF/samples/apps/logging# mkdir build
root@ef5e5f521395:~/CCF/samples/apps/logging# cd build/
root@ef5e5f521395:~/CCF/samples/apps/logging/build#

root@ef5e5f521395:~/CCF/samples/apps/logging/build# CC=$(command -v clang-10) CXX=$(command -v clang++-10) cmake ..
root@ef5e5f521395:~/CCF/samples/apps/logging/build# make
root@ef5e5f521395:~/CCF/samples/apps/logging/build# ls ~/CCF/samples/apps/logging/build/liblogging.virtual.so
/root/CCF/samples/apps/logging/build/liblogging.virtual.so

root@ef5e5f521395:~/CCF/samples/apps/logging/build# cd /root/CCF/samples/config
root@ef5e5f521395:~/CCF/samples/config# ls
join_config.json  minimal_config.json  recover_config.json  start_config.json
root@ef5e5f521395:~/CCF/samples/config# cp start_config.json /root/CCF/samples/apps/logging/build/

#update /root/CCF/samples/apps/logging/build/start_config.json
root@ef5e5f521395:~/CCF/samples/apps/logging/build# cat start_config.json
{
  "enclave": {
    "file": "<strong>/root/CCF/samples/apps/logging/build/liblogging.virtual.so</strong>",
    "type": "<strong>Virtual</strong>"
  },
  "network": {
    "node_to_node_interface": { "bind_address": "<strong>172.17.0.2</strong>:8081" },
    "rpc_interfaces": {
      "primary_rpc_interface": {
        "bind_address": "<strong>172.17.0.2</strong>:8080",
        "published_address": "ccf.dummy.com:12345",
        "max_open_sessions_soft": 1000,
        "max_open_sessions_hard": 1010
      },
      "secondary_rpc_interface": {
        "bind_address": "<strong>172.17.0.2</strong>:8082",
        "published_address": "ccf.dummy.com:12346",
        "max_open_sessions_soft": 1000,
        "max_open_sessions_hard": 1010
      }
    }
  },
  "node_certificate": {
    "subject_name": "CN=CCF Node",
    "subject_alt_names": &#91;"iPAddress:<strong>172.17.0.2</strong>", "dNSName:ccf.dummy.com"],
    "curve_id": "Secp384R1",
    "initial_validity_days": 1
  },
  "command": {
    "type": "Start",
    "service_certificate_file": "service_cert.pem",
    "start": {
      "constitution_files": &#91;
        "validate.js",
        "apply.js",
        "resolve.js",
        "actions.js"
      ],
      "members": &#91;
        {
          "certificate_file": "member0_cert.pem",
          "encryption_public_key_file": "member0_enc_pubk.pem"
        }
      ],
      "service_configuration": {
        "recovery_threshold": 1,
        "maximum_node_certificate_validity_days": 365
      },
      "initial_service_certificate_validity_days": 1
    }
  },
  "ledger": {
    "directory": "ledger",
    "read_only_directories": &#91;],
    "chunk_size": "5MB"
  },
  "snapshots": {
    "directory": "snapshots",
    "tx_count": 10000
  },
  "logging": {
    "host_level": "Info",
    "format": "Text"
  },
  "consensus": {
    "type": "CFT",
    "message_timeout": "100ms",
    "election_timeout": "4000ms"
  },
  "ledger_signatures": {
    "tx_count": 5000,
    "delay": "1s"
  },
  "jwt": {
    "key_refresh_interval": "30min"
  },
  "output_files": {
    "node_certificate_file": "node.pem",
    "pid_file": "node.pid",
    "node_to_node_address_file": "node.node_address",
    "rpc_addresses_file": "node.rpc_addresses"
  },
  "tick_interval": "1ms",
  "slow_io_logging_threshold": "10000us",
  "client_connection_timeout": "2000ms",
  "worker_threads": 0,
  "memory": {
    "circuit_size": "4MB",
    "max_msg_size": "16MB",
    "max_fragment_size": "64KB"
  }
}

root@ef5e5f521395:~/CCF/python/utils# ./keygenerator.sh --name member0 --gen-enc-key
root@ef5e5f521395:~/CCF/python/utils# ls -l member0*
-rw-r--r-- 1 root root  656 Feb 18 12:49 member0_cert.pem
-rw------- 1 root root 1675 Feb 18 12:49 member0_enc_privk.pem
-rw-r--r-- 1 root root  451 Feb 18 12:49 member0_enc_pubk.pem
-rw------- 1 root root  359 Feb 18 12:49 member0_privk.pem

root@ef5e5f521395:~# cd ~/CCF/samples/constitutions/default/
root@ef5e5f521395:~/CCF/samples/constitutions/default# ls
actions.js  apply.js  resolve.js  validate.js
root@ef5e5f521395:~/CCF/samples/constitutions/default# cp * ~/CCF/python/utils/

root@ef5e5f521395:~/CCF/python/utils# /opt/ccf/bin/cchost.virtual --config /root/CCF/samples/apps/logging/build/start_config.json
2022-02-18T12:56:31.486435Z        100 &#91;info ] ../src/host/main.cpp:164             | CCF version: ccf-2.0.0-rc0
2022-02-18T12:56:31.486570Z        100 &#91;info ] ../src/host/main.cpp:172             | Configuration file /root/CCF/samples/apps/logging/build/start_config.json:
...
2022-02-18T12:56:31.492094Z        100 &#91;info ] ../src/host/ledger.h:835             | Recovered ledger entries up to 0, committed to 0
2022-02-18T12:56:31.492393Z        100 &#91;info ] ../src/host/node_connections.h:212   | Listening for node-to-node on 172.17.0.2:8081
2022-02-18T12:56:31.492551Z        100 &#91;info ] ../src/host/rpc_connections.h:75     | Listening for RPCs on 172.17.0.2:8080
2022-02-18T12:56:31.492598Z        100 &#91;info ] ../src/host/rpc_connections.h:75     | Listening for RPCs on 172.17.0.2:8082
2022-02-18T12:56:31.492694Z        100 &#91;info ] ../src/host/main.cpp:420             | Startup host time: 2022-02-18 12:56:31
2022-02-18T12:56:31.493159Z        100 &#91;info ] ../src/host/main.cpp:471             | Creating new node: new network (with 1 initial member(s) and 1 member(s) required for recovery)
2022-02-18T12:56:31.558605Z        100 &#91;info ] ../src/host/main.cpp:559             | Created new node
2022-02-18T12:56:31.558709Z        100 &#91;info ] ../src/host/main.cpp:565             | Output self-signed node certificate to node.pem
2022-02-18T12:56:31.558728Z        100 &#91;info ] ../src/host/main.cpp:574             | Output service certificate to service_cert.pem
2022-02-18T12:56:31.558855Z        0   &#91;info ] ../src/enclave/rpc_sessions.h:168    | Setting max open sessions on interface "primary_rpc_interface" (172.17.0.2:8080) to &#91;1000, 1010]
2022-02-18T12:56:31.558871Z        0   &#91;info ] ../src/enclave/rpc_sessions.h:168    | Setting max open sessions on interface "secondary_rpc_interface" (172.17.0.2:8082) to &#91;1000, 1010]
2022-02-18T12:56:31.558874Z        0   &#91;info ] ../src/node/node_state.h:1558        | Node TLS connections now accepted
2022-02-18T12:56:31.558876Z        0   &#91;info ] ../src/consensus/aft/raft.h:1808     | Becoming leader n&#91;3baffb5486a59cd1951b2412d641f957ffbdf33fb57d6c9cf35faa7d726d02c6]: 2
2022-02-18T12:56:31.558879Z        0   &#91;info ] ../src/node/rpc/node_frontend.h:1314 | Created service
2022-02-18T12:56:31.558958Z        0   &#91;info ] ../src/node/node_state.h:359         | Created new node n&#91;3baffb5486a59cd1951b2412d641f957ffbdf33fb57d6c9cf35faa7d726d02c6]
2022-02-18T12:56:31.560049Z        0   &#91;info ] ../src/enclave/main.cpp:253          | Starting thread: 0
2022-02-18T12:56:31.560075Z        0   &#91;info ] ../src/enclave/main.cpp:260          | All threads are ready!
2022-02-18T12:56:33.891449Z -0.001 0   &#91;info ] ../src/consensus/aft/raft.h:557      | Election timer has become active
2022-02-18T12:56:33.891588Z -0.001 0   &#91;info ] ../src/node/node_state.h:1571        | Network TLS connections now accepted</code></pre>

Open another terminal

root@ef5e5f521395:/# curl -k https://172.17.0.2:8080/node/commit
{"transaction_id":"2.2"}

root@ef5e5f521395:/# curl -k https://172.17.0.2:8080/node/state -X GET
{"last_signed_seqno":2,"node_id":"3baffb5486a59cd1951b2412d641f957ffbdf33fb57d6c9cf35faa7d726d02c6","startup_seqno":0,"state":"PartOfNetwork"}</code></pre>

# activating member0
root@ef5e5f521395:~/CCF/python/utils# curl -X POST  https://172.17.0.2:8080/gov/ack/update_state_digest  --cacert service_cert.pem --key member0_privk.pem --cert member0_cert.pem
{"state_digest":"91aa51ce2ad601879598af8220941fa164da23b1e149c8cc4802392f8f6dca2e"}

root@ef5e5f521395:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/ack  --cacert service_cert.pem --signing-key member0_privk.pem --signing-cert member0_cert.pem --header "Content-Type: application/json" --data-binary '{"state_digest": "91aa51ce2ad601879598af8220941fa164da23b1e149c8cc4802392f8f6dca2e"}'</code></pre>

# open network
root@ef5e5f521395:~/CCF/python/utils# cat transition_service_to_open.json
{
    "actions": &#91;
                    {
                        "name": "transition_service_to_open",
                        "args": null
                    }
    ]
}

root@ef5e5f521395:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/proposals --cacert service_cert.pem --signing-key member0_
privk.pem --signing-cert member0_cert.pem --data-binary @transition_service_to_open.json -H "content-type: application/json"
{"ballot_count":0,"proposal_id":"02fbf266222da72d955efd4d03bbb4ae5ca2ab77d450084fc2a32ea8251a7c9c","proposer_id":"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac","state":"Open"}

# vote to accept the open network proposal
$ cat vote_accept.json
{
  "ballot": "export function vote (proposal, proposerId) { return true }"
}

root@ef5e5f521395:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/proposals/02fbf266222da72d955efd4d03bbb4ae5ca2ab77d450084fc2a32ea8251a7c9c/ballots --cacert service_cert.pem --signing-key member0_privk.pem --signing-cert member0_cert.pem --data-binary @vote_accept.json -H "content-type: application/json"
{"ballot_count":1,"proposal_id":"02fbf266222da72d955efd4d03bbb4ae5ca2ab77d450084fc2a32ea8251a7c9c","proposer_id":"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac","state":"Accepted","votes":{"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac":true}}</code></pre>

# JavaScript bundle to be deployed

#full set_js_app.json
{
  "actions": &#91;
    {
      "name": "set_js_app",
      "args": {
        "bundle": {
          "metadata": {
            "endpoints": {
              "/compute": {
                "post": {
                  "js_module": "math.js",
                  "js_function": "compute",
                  "forwarding_required": "never",
                  "authn_policies": &#91;
                    "user_cert"
                  ],
                  "mode": "readonly",
                  "openapi": {
                    "requestBody": {
                      "required": true,
                      "content": {
                        "application/json": {
                          "schema": {
                            "properties": {
                              "op": {
                                "type": "string",
                                "enum": &#91;
                                  "add",
                                  "sub",
                                  "mul"
                                ]
                              },
                              "left": {
                                "type": "number"
                              },
                              "right": {
                                "type": "number"
                              }
                            },
                            "required": &#91;
                              "op",
                              "left",
                              "right"
                            ],
                            "type": "object",
                            "additionalProperties": false
                          }
                        }
                      }
                    },
                    "responses": {
                      "200": {
                        "description": "Compute result",
                        "content": {
                          "application/json": {
                            "schema": {
                              "properties": {
                                "result": {
                                  "type": "number"
                                }
                              },
                              "required": &#91;
                                "result"
                              ],
                              "type": "object",
                              "additionalProperties": false
                            }
                          }
                        }
                      },
                      "400": {
                        "description": "Client-side error",
                        "content": {
                          "application/json": {
                            "schema": {
                              "properties": {
                                "error": {
                                  "description": "Error message",
                                  "type": "string"
                                }
                              },
                              "required": &#91;
                                "error"
                              ],
                              "type": "object",
                              "additionalProperties": false
                            }
                          }
                        }
                      }
                    }
                  }
                }
              },
              "/compute2/{op}/{left}/{right}": {
                "get": {
                  "js_module": "math.js",
                  "js_function": "compute2",
                  "forwarding_required": "never",
                  "authn_policies": &#91;
                    "user_cert"
                  ],
                  "mode": "readonly",
                  "openapi": {
                    "parameters": &#91;
                      {
                        "name": "op",
                        "in": "path",
                        "required": true,
                        "schema": {
                          "type": "string",
                          "enum": &#91;
                            "add",
                            "sub",
                            "mul"
                          ]
                        }
                      },
                      {
                        "name": "left",
                        "in": "path",
                        "required": true,
                        "schema": {
                          "type": "number"
                        }
                      },
                      {
                        "name": "right",
                        "in": "path",
                        "required": true,
                        "schema": {
                          "type": "number"
                        }
                      }
                    ],
                    "responses": {
                      "default": {
                        "description": "Default response"
                      }
                    }
                  }
                }
              }
            }
          },
          "modules": &#91;
            {
              "name": "math.js",
              "module": "function compute_impl(op, left, right) {\n  let result;\n  if (op == \"add\") result = left + right;\n  else if (op == \"sub\") result = left - right;\n  else if (op == \"mul\") result = left * right;\n  else {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"unknown op\",\n      },\n    };\n  }\n\n  return {\n    body: {\n      result: result,\n    },\n  };\n}\n\nexport function compute(request) {\n  const body = request.body.json();\n\n  if (typeof body.left != \"number\" || typeof body.right != \"number\") {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"invalid operand type\",\n      },\n    };\n  }\n\n  return compute_impl(body.op, body.left, body.right);\n}\n\nexport function compute2(request) {\n  const params = request.params;\n\n  // Type of params is always string. Try to parse as float\n  let left = parseFloat(params.left);\n  if (isNaN(left)) {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"left operand is not a parseable number\",\n      },\n    };\n  }\n\n  let right = parseFloat(params.right);\n  if (isNaN(right)) {\n    return {\n      statusCode: 400,\n      body: {\n        error: \"right operand is not a parseable number\",\n      },\n    };\n  }\n\n  return compute_impl(params.op, left, right);\n}\n"
            }
          ]
        },
        "disable_bytecode_cache": false
      }
    }
  ]
}

# raise deploy bundle js proposal
root@ef5e5f521395:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/proposals --cacert service_cert.pem --signing-cert member0_cert.pem --signing-key member0_privk.pem --data-binary @set_js_app.json -H "content-type: application/json"
{"ballot_count":0,"proposal_id":"5ae4c4f24279ffe4425859d36d91ec8364156948a7431b52661e6c7935a1f3ce","proposer_id":"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac","state":"Open"}

# get proposal status
root@ef5e5f521395:~/CCF/python/utils# curl -X GET  https://172.17.0.2:8080/gov/proposals/5ae4c4f24279ffe4425859d36d91ec8364156948a7431b52661e6c7935a1f3ce  --cacert service_cert.pem --key member0_privk.pem --cert member0_cert.pem
{"ballots":{},"proposer_id":"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac","state":"Open"}

# approve deploy bundle js proposal
root@ef5e5f521395:~/CCF/python/utils# cat vote_accept.json
{
          "ballot": "export function vote (proposal, proposerId) { return true }"
}
root@ef5e5f521395:~/CCF/python/utils# cat vote_reject.json
{
          "ballot": "export function vote (proposal, proposerId) { return false }"
}

root@ef5e5f521395:~/CCF/python/utils# ./scurl.sh https://172.17.0.2:8080/gov/proposals/5ae4c4f24279ffe4425859d36d91ec8364156948a7431b52661e6c7935a1f3ce/ballots --cacert service_cert.pem --signing-key member0_privk.pem --signing-cert member0_cert.pem --data-binary @vote_accept.json -H "content-type: application/json"
{"ballot_count":1,"proposal_id":"5ae4c4f24279ffe4425859d36d91ec8364156948a7431b52661e6c7935a1f3ce","proposer_id":"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac","state":"Accepted","votes":{"4d598d058fc1317dd9087c8100cfe8f53a9cb6dbf2616e7efb62d964122bcaac":true}}

# call the end point
root@ef5e5f521395:~/CCF/python/utils# curl https://172.17.0.2:8080/app/compute2/add/3/5 -X GET --cacert service_cert.pem --cert member0_cert.pem --key member0_privk.pem
{"error":{"code":"ResourceNotFound","message":"Unknown path: /compute2/add/3/5."}}

root@ef5e5f521395:~/CCF/python/utils# curl https://172.17.0.2:8080/app/api -X GET --cacert service_cert.pem --cert member0_cert.pem --key member0_privk.pem
eddyashton commented 2 years ago

@davidzhongsydney Ah, the problem here is the difference between the sample apps liblogging and libjs_generic. You're starting a node running the liblogging application, but the set_js_app proposal (and JS docs in general) only apply to the libjs_generic application.

So the short answer is: to experiment with any of the JS tutorials or JS app development, replace your paths to liblogging.virtual.so with the path to libjs_generic.virtual.so (from the CCF deb or Docker images, this should be installed with the framework at /opt/ccf/lib/libjs_generic.virtual.so). If you want to build off the logging sample app for your own C++ application, then ignore the JS steps in the docs.

Further explanation: liblogging is a sample application written in C++ which exposes a few hard-coded endpoints (/app/log/private etc). These /app endpoints are available immediately after opening a service. To update or change any of the endpoint behaviour requires a governance proposal to change the accepted code IDs, and new nodes (running with a new build of liblogging) to be added to the network.

libjs_generic is an application which contains a JS runtime, and allows endpoints to be dynamically registered and modified at runtime. When you open a service running libjs_generic, it initially has no /app endpoints, and you can add/change the endpoints by passing set_js_app proposals. Crucially these can be swapped in by governance on existing nodes/services, without requiring a restart, so the turnaround time is faster (at the cost of all endpoint execution going through a JS interpreter).

This was non-obvious here because set_js_app should really only be present on services running libjs_generic, since it does nothing on other services (it writes the given modules to a table, but has no JS interpreter/dispatch logic to read or execute them). We currently only ship single copies of the sample constitution files for use across all of our sample applications. We'll look at splitting those up, so proposals which have no meaning for the current app will return an earlier error.

davidzhongsydney commented 2 years ago

Thanks @eddyashton. Now it works. Cheers.