Skip to content
This repository has been archived by the owner on Nov 16, 2022. It is now read-only.

runtime: Patch google cloud function #2494

Merged
merged 15 commits into from
Nov 12, 2020
2 changes: 2 additions & 0 deletions CHANGELOG_UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@

### Runtime

- (impv) [\#2494](https://github.com/bandprotocol/bandchain/pull/2494) Patch google cloud function

### Owasm

### Oracle Binary Encoding (OBI)
Expand Down
4 changes: 4 additions & 0 deletions runtime/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/docker/requirements.txt

/google-cloud-functions/venv
/google-cloud-functions/requirements.txt
/google-cloud-functions/google-cloud-function.zip

/lambda/requirements.txt
/lambda/venv
/lambda/lambda-yoda.zip
11 changes: 10 additions & 1 deletion runtime/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: lambda

VERSION=1.1.1
VERSION=1.1.2
CURRENT_DIR=$(shell pwd)

lambda:
Expand All @@ -18,3 +18,12 @@ docker-build:

docker-push: docker-build
docker push bandprotocol/runtime:$(VERSION)

gcf:
mv $(CURRENT_DIR)/google-cloud-functions/main.py $(CURRENT_DIR)/google-cloud-functions/main.bak
RUNTIME_VERSION="google-cloud-function:$(VERSION)" envsubst < $(CURRENT_DIR)/google-cloud-functions/google_cloud_function.py > $(CURRENT_DIR)/google-cloud-functions/main.py
cat requirements.txt $(CURRENT_DIR)/google-cloud-functions/extra_requirements.txt >> $(CURRENT_DIR)/google-cloud-functions/requirements.txt
cd google-cloud-functions && zip google_cloud_function.zip main.py requirements.txt && cd ..
mv $(CURRENT_DIR)/google-cloud-functions/main.bak $(CURRENT_DIR)/google-cloud-functions/main.py
rm $(CURRENT_DIR)/google-cloud-functions/requirements.txt

Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
attrs==19.3.0
click==7.1.2
Flask==1.1.2
importlib-metadata==1.6.1
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
marshmallow==3.6.1
more-itertools==8.3.0
packaging==20.4
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.7
pytest==5.4.3
six==1.15.0
wcwidth==0.2.4
Werkzeug==1.0.1
zipp==3.1.0
88 changes: 37 additions & 51 deletions runtime/google-cloud-functions/google_cloud_function.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from flask import Flask, jsonify, json
from flask import jsonify
import os
import shlex
import subprocess
import base64
import werkzeug
from marshmallow import Schema, fields, ValidationError, validate

# Copy and paste this file on Google Cloud function
# Set environment flag of MAX_EXECUTABLE, MAX_CALLDATA, MAX_TIMEOUT, MAX_STDOUT, MAX_STDERR
# Set environment flag of MAX_EXECUTABLE, MAX_DATA_SIZE


runtime_version = "${RUNTIME_VERSION}"


def get_env(env, flag):
Expand All @@ -19,7 +19,13 @@ def get_env(env, flag):
def success(returncode, stdout, stderr, err):
return (
jsonify(
{"returncode": returncode, "stdout": stdout, "stderr": stderr, "err": err}
{
"returncode": returncode,
"stdout": stdout,
"stderr": stderr,
"err": err,
"version": runtime_version,
}
),
200,
)
Expand All @@ -41,65 +47,45 @@ def execute(request):
env = os.environ.copy()

MAX_EXECUTABLE = get_env(env, "MAX_EXECUTABLE")
MAX_CALLDATA = get_env(env, "MAX_CALLDATA")
MAX_TIMEOUT = get_env(env, "MAX_TIMEOUT")
MAX_STDOUT = get_env(env, "MAX_STDOUT")
MAX_STDERR = get_env(env, "MAX_STDERR")

class Executable(fields.Field):
def _deserialize(self, value, attr, data, **kwargs):
try:
return base64.b64decode(value).decode()
except:
raise ValidationError("Can't decoded executable")

class RequestSchema(Schema):
executable = Executable(
required=True,
validate=validate.Length(max=MAX_EXECUTABLE),
error_messages={"required": "field is missing from JSON request"},
)
calldata = fields.Str(
required=True,
validate=validate.Length(max=MAX_CALLDATA),
error_messages={"required": "field is missing from JSON request"},
)
timeout = fields.Int(
required=True,
validate=validate.Range(min=0, max=MAX_TIMEOUT),
error_messages={"required": "field is missing from JSON request"},
)

try:
request_json = request.get_json(force=True)
except werkzeug.exceptions.BadRequest:
return bad_request("invalid JSON request format")

MAX_DATA_SIZE = get_env(env, "MAX_DATA_SIZE")

request_json = request.get_json(force=True)
if "executable" not in request_json:
return bad_request("Missing executable value")
if len(request_json["executable"]) > MAX_EXECUTABLE:
return bad_request("Executable exceeds max size")
if "calldata" not in request_json:
return bad_request("Missing calldata value")
if len(request_json["calldata"]) > MAX_DATA_SIZE:
return bad_request("Calldata exceeds max size")
if "timeout" not in request_json:
return bad_request("Missing timeout value")
try:
loaded_request = RequestSchema().load(request_json)
except ValidationError as err:
return bad_request(err.messages)
timeout = int(request_json["timeout"])
except ValueError:
return bad_request("Timeout format invalid")

path = "/tmp/execute.sh"
with open(path, "w") as f:
f.write(loaded_request["executable"])
f.write(base64.b64decode(request_json["executable"]).decode())

os.chmod(path, 0o775)

try:
timeout_millisec = loaded_request["timeout"]
timeout_sec = timeout_millisec / 1000
env = os.environ.copy()
for key, value in request_json.get("env", {}).items():
env[key] = value

proc = subprocess.Popen(
[path] + shlex.split(loaded_request["calldata"]),
[path] + shlex.split(request_json["calldata"]),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

proc.wait(timeout=timeout_sec)
proc.wait(timeout=(timeout / 1000))
returncode = proc.returncode
stdout = proc.stdout.read(MAX_STDOUT).decode()
stderr = proc.stderr.read(MAX_STDERR).decode()
stdout = proc.stdout.read(MAX_DATA_SIZE).decode()
stderr = proc.stderr.read(MAX_DATA_SIZE).decode()
return success(returncode, stdout, stderr, "")
except OSError:
return success(126, "", "", "Execution fail")
Expand Down
Loading