Skip to content

Commit 04d5806

Browse files
feat: use gcs for stream file storage (#994)
Signed-off-by: Jeffrey Tang <[email protected]> Signed-off-by: JeffreyDallas <[email protected]> Signed-off-by: Jeromy Cannon <[email protected]> Co-authored-by: Jeromy Cannon <[email protected]>
1 parent 5b9d937 commit 04d5806

15 files changed

+542
-46
lines changed

.github/workflows/flow-gcs-test.yaml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
##
2+
# Copyright (C) 2023-2024 Hedera Hashgraph, LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
##
16+
17+
name: "Test GCS as bucket storage"
18+
19+
on:
20+
workflow_dispatch:
21+
workflow_call:
22+
23+
defaults:
24+
run:
25+
shell: bash
26+
27+
permissions:
28+
id-token: write
29+
contents: read
30+
actions: read
31+
32+
jobs:
33+
gcs-storage-test:
34+
timeout-minutes: 20
35+
runs-on: solo-linux-large
36+
strategy:
37+
matrix:
38+
storageType: ["gcs_only", "gcs_and_minio"]
39+
steps:
40+
- name: Harden Runner
41+
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
42+
with:
43+
egress-policy: audit
44+
45+
- name: Checkout Code
46+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
47+
48+
- name: Authenticate to Google Cloud
49+
id: google-auth
50+
uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7
51+
with:
52+
workload_identity_provider: "projects/652966097426/locations/global/workloadIdentityPools/solo-bucket-dev-pool/providers/gh-provider"
53+
service_account: "solo-bucket-reader-writer@solo-bucket-dev.iam.gserviceaccount.com"
54+
55+
- name: Setup Google Cloud SDK
56+
uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a # v2.1.2
57+
58+
- name: Get Current Job Log URL
59+
uses: Tiryoh/gha-jobid-action@v1
60+
id: jobs
61+
with:
62+
github_token: ${{ secrets.GITHUB_TOKEN }}
63+
job_name: "gcs-storage-test (${{ matrix.storageType }})"
64+
65+
- name: Create GCS bucket
66+
# create a new bucket and use job runner id as prefix
67+
run: |
68+
export BUCKET_NAME=${{ steps.jobs.outputs.job_id }}-solo-streams
69+
gcloud storage buckets create gs://${BUCKET_NAME} --project=${{ vars.GCP_S3_PROJECT_ID }}
70+
echo "BUCKET_NAME=${BUCKET_NAME}" >> $GITHUB_ENV
71+
72+
- name: Setup Node
73+
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
74+
with:
75+
node-version: 20
76+
cache: npm
77+
78+
- name: Setup Helm
79+
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
80+
with:
81+
version: "v3.12.3" # helm version
82+
83+
- name: Setup Kind
84+
uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
85+
with:
86+
install_only: true
87+
node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
88+
version: v0.21.0
89+
kubectl_version: v1.28.6
90+
verbosity: 3
91+
wait: 120s
92+
93+
- name: Install Dependencies
94+
id: npm-deps
95+
run: |
96+
npm ci
97+
npm install -g @hashgraph/solo
98+
99+
- name: Compile Project
100+
run: npm run build
101+
102+
- name: Run GCS Test Script for type ${{ matrix.channel }}
103+
env:
104+
GCS_ACCESS_KEY: ${{ secrets.GCP_S3_ACCESS_KEY }}
105+
GCS_SECRET_KEY: ${{ secrets.GCP_S3_SECRET_KEY }}
106+
BUCKET_NAME: ${{ env.BUCKET_NAME }}
107+
STORAGE_TYPE: ${{ matrix.storageType }}
108+
run: |
109+
.github/workflows/script/gcs_test.sh
110+
111+
- name: Delete Bucket after Test
112+
run: |
113+
gcloud storage rm --recursive gs://${BUCKET_NAME} --project=${{ vars.GCP_S3_PROJECT_ID }}

.github/workflows/script/gcs_test.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
set -eo pipefail
3+
4+
source .github/workflows/script/helper.sh
5+
6+
if [ -z "${GCS_ACCESS_KEY}" ]; then
7+
echo "GCS_ACCESS_KEY is not set. Exiting..."
8+
exit 1
9+
fi
10+
11+
if [ -z "${GCS_SECRET_KEY}" ]; then
12+
echo "GCS_SECRET_KEY is not set. Exiting..."
13+
exit 1
14+
fi
15+
16+
if [ -z "${BUCKET_NAME}" ]; then
17+
streamBucket="solo-ci-test-streams"
18+
else
19+
streamBucket=${BUCKET_NAME}
20+
fi
21+
22+
if [ -z "${STORAGE_TYPE}" ]; then
23+
storageType="gcs_and_minio"
24+
else
25+
storageType=${STORAGE_TYPE}
26+
fi
27+
28+
echo "Using bucket name: ${streamBucket}"
29+
echo "Test storage type: ${storageType}"
30+
31+
SOLO_CLUSTER_NAME=solo-e2e
32+
SOLO_NAMESPACE=solo-e2e
33+
SOLO_CLUSTER_SETUP_NAMESPACE=solo-setup
34+
35+
kind delete cluster -n "${SOLO_CLUSTER_NAME}"
36+
kind create cluster -n "${SOLO_CLUSTER_NAME}"
37+
npm run solo-test -- init
38+
npm run solo-test -- cluster setup \
39+
-s "${SOLO_CLUSTER_SETUP_NAMESPACE}"
40+
npm run solo-test -- node keys --gossip-keys --tls-keys -i node1
41+
npm run solo-test -- network deploy -i node1 -n "${SOLO_NAMESPACE}" \
42+
--storage-endpoint "https://storage.googleapis.com" \
43+
--storage-access-key "${GCS_ACCESS_KEY}" --storage-secrets "${GCS_SECRET_KEY}" \
44+
--storage-type "${storageType}" --storage-bucket "${streamBucket}"
45+
46+
npm run solo-test -- node setup -i node1 -n "${SOLO_NAMESPACE}"
47+
npm run solo-test -- node start -i node1 -n "${SOLO_NAMESPACE}"
48+
npm run solo-test -- mirror-node deploy --namespace "${SOLO_NAMESPACE}" \
49+
--storage-endpoint "https://storage.googleapis.com" \
50+
--storage-access-key "${GCS_ACCESS_KEY}" --storage-secrets "${GCS_SECRET_KEY}" \
51+
--storage-type "${storageType}" --storage-bucket "${streamBucket}"
52+
53+
kubectl port-forward -n "${SOLO_NAMESPACE}" svc/haproxy-node1-svc 50211:50211 > /dev/null 2>&1 &
54+
kubectl port-forward -n "${SOLO_NAMESPACE}" svc/hedera-explorer 8080:80 > /dev/null 2>&1 &
55+
56+
cd ..; create_test_account ; cd -
57+
58+
node examples/create-topic.js
59+
60+
npm run solo-test -- node stop -i node1 -n "${SOLO_NAMESPACE}"

.github/workflows/script/helper.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash
2+
set -eo pipefail
3+
4+
function create_test_account ()
5+
{
6+
echo "Create test account with solo network"
7+
cd solo
8+
9+
# create new account and extract account id
10+
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
11+
export OPERATOR_ID=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
12+
echo "OPERATOR_ID=${OPERATOR_ID}"
13+
rm test.log
14+
15+
# get private key of the account
16+
npm run solo-test -- account get -n solo-e2e --account-id ${OPERATOR_ID} --private-key > test.log
17+
export OPERATOR_KEY=$(grep "privateKey" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
18+
export CONTRACT_TEST_KEY_ONE=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
19+
echo "CONTRACT_TEST_KEY_ONE=${CONTRACT_TEST_KEY_ONE}"
20+
rm test.log
21+
22+
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
23+
export SECOND_KEY=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
24+
npm run solo-test -- account get -n solo-e2e --account-id ${SECOND_KEY} --private-key > test.log
25+
export CONTRACT_TEST_KEY_TWO=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
26+
echo "CONTRACT_TEST_KEY_TWO=${CONTRACT_TEST_KEY_TWO}"
27+
rm test.log
28+
29+
export CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEY_ONE},$'\n'${CONTRACT_TEST_KEY_TWO}
30+
export HEDERA_NETWORK="local-node"
31+
32+
echo "OPERATOR_KEY=${OPERATOR_KEY}"
33+
echo "HEDERA_NETWORK=${HEDERA_NETWORK}"
34+
echo "CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEYS}"
35+
36+
cd -
37+
}

.github/workflows/script/solo_smoke_test.sh

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ set -eo pipefail
99
# Then run smart contract test, and also javascript sdk sample test to interact with solo network
1010
#
1111

12-
function_name=""
12+
source .github/workflows/script/helper.sh
1313

1414
function enable_port_forward ()
1515
{
@@ -73,41 +73,6 @@ function start_contract_test ()
7373
return $result
7474
}
7575

76-
function create_test_account ()
77-
{
78-
echo "Create test account with solo network"
79-
cd solo
80-
81-
# create new account and extract account id
82-
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
83-
export OPERATOR_ID=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
84-
echo "OPERATOR_ID=${OPERATOR_ID}"
85-
rm test.log
86-
87-
# get private key of the account
88-
npm run solo-test -- account get -n solo-e2e --account-id ${OPERATOR_ID} --private-key > test.log
89-
export OPERATOR_KEY=$(grep "privateKey" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
90-
export CONTRACT_TEST_KEY_ONE=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
91-
echo "CONTRACT_TEST_KEY_ONE=${CONTRACT_TEST_KEY_ONE}"
92-
rm test.log
93-
94-
npm run solo-test -- account create -n solo-e2e --hbar-amount 100 --generate-ecdsa-key --set-alias > test.log
95-
export SECOND_KEY=$(grep "accountId" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
96-
npm run solo-test -- account get -n solo-e2e --account-id ${SECOND_KEY} --private-key > test.log
97-
export CONTRACT_TEST_KEY_TWO=0x$(grep "privateKeyRaw" test.log | awk '{print $2}' | sed 's/"//g'| sed 's/,//g')
98-
echo "CONTRACT_TEST_KEY_TWO=${CONTRACT_TEST_KEY_TWO}"
99-
rm test.log
100-
101-
export CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEY_ONE},$'\n'${CONTRACT_TEST_KEY_TWO}
102-
export HEDERA_NETWORK="local-node"
103-
104-
echo "OPERATOR_KEY=${OPERATOR_KEY}"
105-
echo "HEDERA_NETWORK=${HEDERA_NETWORK}"
106-
echo "CONTRACT_TEST_KEYS=${CONTRACT_TEST_KEYS}"
107-
108-
cd -
109-
}
110-
11176
function start_sdk_test ()
11277
{
11378
cd solo

examples/create-topic.js

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,18 @@
1414
* limitations under the License.
1515
*
1616
*/
17-
import {Wallet, LocalProvider, TopicCreateTransaction, TopicMessageSubmitTransaction} from '@hashgraph/sdk';
17+
import {
18+
Wallet,
19+
LocalProvider,
20+
TopicCreateTransaction,
21+
TopicMessageSubmitTransaction,
22+
AccountCreateTransaction,
23+
PrivateKey,
24+
Hbar,
25+
} from '@hashgraph/sdk';
1826

1927
import dotenv from 'dotenv';
28+
import http from 'http';
2029

2130
dotenv.config();
2231

@@ -30,12 +39,11 @@ async function main() {
3039

3140
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);
3241

42+
const TEST_MESSAGE = 'Hello World';
3343
try {
34-
console.log('before create topic');
3544
// create topic
3645
let transaction = await new TopicCreateTransaction().freezeWithSigner(wallet);
3746
transaction = await transaction.signWithSigner(wallet);
38-
console.log('after sign transaction');
3947
const createResponse = await transaction.executeWithSigner(wallet);
4048
const createReceipt = await createResponse.getReceiptWithSigner(wallet);
4149

@@ -44,18 +52,73 @@ async function main() {
4452
// send one message
4553
let topicMessageSubmitTransaction = await new TopicMessageSubmitTransaction({
4654
topicId: createReceipt.topicId,
47-
message: 'Hello World',
55+
message: TEST_MESSAGE,
4856
}).freezeWithSigner(wallet);
4957
topicMessageSubmitTransaction = await topicMessageSubmitTransaction.signWithSigner(wallet);
5058
const sendResponse = await topicMessageSubmitTransaction.executeWithSigner(wallet);
5159

5260
const sendReceipt = await sendResponse.getReceiptWithSigner(wallet);
5361

5462
console.log(`topic sequence number = ${sendReceipt.topicSequenceNumber.toString()}`);
63+
64+
await new Promise(resolve => setTimeout(resolve, 1000));
65+
66+
// send a create account transaction to push record stream files to mirror node
67+
const newKey = PrivateKey.generate();
68+
let accountCreateTransaction = await new AccountCreateTransaction()
69+
.setInitialBalance(new Hbar(10))
70+
.setKey(newKey.publicKey)
71+
.freezeWithSigner(wallet);
72+
accountCreateTransaction = await accountCreateTransaction.signWithSigner(wallet);
73+
const accountCreationResponse = await accountCreateTransaction.executeWithSigner(wallet);
74+
const accountCreationReceipt = await accountCreationResponse.getReceiptWithSigner(wallet);
75+
console.log(`account id = ${accountCreationReceipt.accountId.toString()}`);
76+
77+
await new Promise(resolve => setTimeout(resolve, 1000));
78+
79+
// Check submit message result should success
80+
const queryURL = `http://localhost:8080/api/v1/topics/${createReceipt.topicId}/messages`;
81+
let received = false;
82+
let receivedMessage = '';
83+
84+
// wait until the transaction reached consensus and retrievable from the mirror node API
85+
let retry = 0;
86+
while (!received && retry < 10) {
87+
const req = http.request(queryURL, {method: 'GET', timeout: 100, headers: {Connection: 'close'}}, res => {
88+
res.setEncoding('utf8');
89+
res.on('data', chunk => {
90+
// convert chunk to json object
91+
const obj = JSON.parse(chunk);
92+
if (obj.messages.length === 0) {
93+
console.log('No messages yet');
94+
} else {
95+
// convert message from base64 to utf-8
96+
const base64 = obj.messages[0].message;
97+
const buff = Buffer.from(base64, 'base64');
98+
receivedMessage = buff.toString('utf-8');
99+
console.log(`Received message: ${receivedMessage}`);
100+
received = true;
101+
}
102+
});
103+
});
104+
req.on('error', e => {
105+
console.log(`problem with request: ${e.message}`);
106+
});
107+
req.end(); // make the request
108+
// wait and try again
109+
await new Promise(resolve => setTimeout(resolve, 1000));
110+
retry++;
111+
}
112+
if (receivedMessage === TEST_MESSAGE) {
113+
console.log('Message received successfully');
114+
} else {
115+
console.error('Message received but not match: ' + receivedMessage);
116+
// eslint-disable-next-line n/no-process-exit
117+
process.exit(1);
118+
}
55119
} catch (error) {
56120
console.error(error);
57121
}
58-
59122
provider.close();
60123
}
61124

0 commit comments

Comments
 (0)