Skip to content

Commit

Permalink
Merge pull request #6 from lsd-ucsc/upd-gas-eval
Browse files Browse the repository at this point in the history
- Updated gas cost evaluation for publishing contract call
- Added gas cost evaluation for subscribing contract call
- Added gas cost evaluation into GitHub Action release workflow
- Removed old gas cost evaluation scripts
  • Loading branch information
zhenghaven authored Apr 26, 2023
2 parents fbee95b + db67b99 commit 6ce802c
Show file tree
Hide file tree
Showing 7 changed files with 767 additions and 285 deletions.
56 changes: 55 additions & 1 deletion .github/workflows/create-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ jobs:
strategy:
matrix:
os: [ ubuntu-22.04 ]

python-version: [ 3.11 ]
node-version: [ 18.16.0 ]

solc-version: [ 0.8.19 ]
ganache-version: [ 7.8.0 ]

numpy-version: [ 1.24.3 ]
matplotlib-version: [ 3.7.1 ]
web3-version: [ 6.2.0 ]

opt-flags: [ "--optimize --optimize-runs 200" ]

name: A job to create a release
steps:
- name: Checkout
Expand All @@ -24,9 +34,21 @@ jobs:
with:
node-version: ${{ matrix.node-version }}

- name: Installing solc compiler
- name: Installing Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Installing NPM packages
run: |
npm install -g solc@${{ matrix.solc-version }}
npm install -g ganache@${{ matrix.ganache-version }}
- name: Installing Python packages
run: |
python3 -m pip install web3==${{ matrix.web3-version }}
python3 -m pip install numpy==${{ matrix.numpy-version }}
python3 -m pip install matplotlib==${{ matrix.matplotlib-version }}
- name: Compiling contracts for PubSub/EventManager.sol
run: |
Expand Down Expand Up @@ -67,6 +89,32 @@ jobs:
sha256sum ./build/HelloWorldSubscriber.bin >> ./build/checksums.txt
sha256sum ./build/HelloWorldSubscriber.abi >> ./build/checksums.txt
- name: Prepare binaries for gas cost evaluation
run: |
mkdir -p ./build/PubSub
cp ./build/EventManager.bin ./build/PubSub/EventManager.bin
cp ./build/EventManager.abi ./build/PubSub/EventManager.abi
cp ./build/PubSubService.bin ./build/PubSub/PubSubService.bin
cp ./build/PubSubService.abi ./build/PubSub/PubSubService.abi
mkdir -p ./build/tests
cp ./build/HelloWorldPublisher.bin ./build/tests/HelloWorldPublisher.bin
cp ./build/HelloWorldPublisher.abi ./build/tests/HelloWorldPublisher.abi
cp ./build/HelloWorldSubscriber.bin ./build/tests/HelloWorldSubscriber.bin
cp ./build/HelloWorldSubscriber.abi ./build/tests/HelloWorldSubscriber.abi
- name: Run publish gas cost evaluation
run: |
python3 ./tests/MultiSubsGasCostEval.py
- name: Run subscribe gas cost evaluation
run: |
python3 ./tests/MultiPubsGasCostEval.py
- name: Convert figures into inlined SVG
run: |
python3 ./utils/SvgToInlineMd.py --input ./build/publish_gas_cost.svg --output ./build/publish_gas_cost.md --title "Gas Cost of Publishing Events"
python3 ./utils/SvgToInlineMd.py --input ./build/subscribe_gas_cost.svg --output ./build/subscribe_gas_cost.md --title "Gas cost of Subscribing to Publishers"
- name: Generate release note
run: |
echo "# Release note" >> ./build/release_note.md
Expand All @@ -87,6 +135,12 @@ jobs:
cat ./build/checksums.txt >> ./build/release_note.md
echo "\`\`\`" >> ./build/release_note.md
echo "" >> ./build/release_note.md
echo "## Gas Cost Evaluations" >> ./build/release_note.md
echo "### Gas Cost of Publishing Events" >> ./build/release_note.md
cat ./build/publish_gas_cost.md >> ./build/release_note.md
echo "### Gas cost of Subscribing to Publishers" >> ./build/release_note.md
cat ./build/subscribe_gas_cost.md >> ./build/release_note.md
echo "" >> ./build/release_note.md
- name: Release
uses: softprops/action-gh-release@v1
Expand Down
291 changes: 291 additions & 0 deletions tests/MultiPubsGasCostEval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# Copyright (c) 2023 Roy Shadmon, Haofan Zheng
# Use of this source code is governed by an MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
###


import os
import signal
import subprocess
import sys
import time

import matplotlib.pyplot as plt
import numpy as np

from web3 import Web3
from typing import List, Tuple


BASE_DIR_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BUILD_DIR_PATH = os.path.join(BASE_DIR_PATH, 'build')
UTILS_DIR_PATH = os.path.join(BASE_DIR_PATH, 'utils')
PROJECT_CONFIG_PATH = os.path.join(UTILS_DIR_PATH, 'project_conf.json')
CHECKSUM_KEYS_PATH = os.path.join(UTILS_DIR_PATH, 'ganache_keys_checksum.json')
GANACHE_KEYS_PATH = os.path.join(UTILS_DIR_PATH, 'ganache_keys.json')
GANACHE_PORT = 7545

sys.path.append(UTILS_DIR_PATH)
import EthContractHelper


def StartGanache() -> subprocess.Popen:
cmd = [
'ganache-cli',
'-p', str(GANACHE_PORT),
'-d',
'-a', '20',
'--network-id', '1337',
'--wallet.accountKeysPath', GANACHE_KEYS_PATH,
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

return proc


def RunTests() -> List[Tuple[int, int]]:
maxNumPublishers = 20

# connect to ganache
ganacheUrl = 'http://localhost:{}'.format(GANACHE_PORT)
w3 = Web3(Web3.HTTPProvider(ganacheUrl))
while not w3.is_connected():
print('Attempting to connect to ganache...')
time.sleep(1)
print('Connected to ganache')

# setup account
privKey = EthContractHelper.SetupSendingAccount(
w3=w3,
account=0, # use account 0
keyJson=CHECKSUM_KEYS_PATH
)


subscribeCost = []


for numPublishers in range(1, maxNumPublishers + 1):
print()
print(f'Running test with {numPublishers} subscribers')
print()

# deploy PubSub contract
print('Deploying PubSub contract...')
pubSubContract = EthContractHelper.LoadContract(
w3=w3,
projConf=PROJECT_CONFIG_PATH,
contractName='PubSubService',
release=None, # use locally built contract
address=None, # deploy new contract
)
pubSubReceipt = EthContractHelper.DeployContract(
w3=w3,
contract=pubSubContract,
arguments=[ ],
privKey=privKey,
gas=None, # let web3 estimate
value=0,
confirmPrompt=False # don't prompt for confirmation
)
pubSubAddr = pubSubReceipt.contractAddress
print('PubSub contract deployed at {}'.format(pubSubAddr))

# load deployed PubSub contract
pubSubContract = EthContractHelper.LoadContract(
w3=w3,
projConf=PROJECT_CONFIG_PATH,
contractName='PubSubService',
release=None, # use locally built contract
address=pubSubAddr, # use deployed contract
)

publishers = []
for pubIndex in range(0, numPublishers):
# deploy Publisher contract
print('Deploying publisher contract...')
publisherContract = EthContractHelper.LoadContract(
w3=w3,
projConf=PROJECT_CONFIG_PATH,
contractName='HelloWorldPublisher',
release=None, # use locally built contract
address=None, # deploy new contract
)
publisherReceipt = EthContractHelper.DeployContract(
w3=w3,
contract=publisherContract,
arguments=[ ],
privKey=privKey,
gas=None, # let web3 estimate
value=0,
confirmPrompt=False # don't prompt for confirmation
)
publisherAddr = publisherReceipt.contractAddress
print('Publisher contract deployed at {}'.format(publisherAddr))

# load deployed Publisher contract
publisherContract = EthContractHelper.LoadContract(
w3=w3,
projConf=PROJECT_CONFIG_PATH,
contractName='HelloWorldPublisher',
release=None, # use locally built contract
address=publisherAddr, # use deployed contract
)

# register publisher
print('Registering publisher...')
EthContractHelper.CallContractFunc(
w3=w3,
contract=publisherContract,
funcName='register',
arguments=[ pubSubAddr ],
privKey=privKey,
gas=None, # let web3 estimate
value=0,
confirmPrompt=False # don't prompt for confirmation
)

publishers.append(publisherContract)

costs = []
for publisherContract in publishers:
publisherAddr = publisherContract.address

# deploy Subscriber contract
print('Deploying subscriber contract...')
subscriberContract = EthContractHelper.LoadContract(
w3=w3,
projConf=PROJECT_CONFIG_PATH,
contractName='HelloWorldSubscriber',
release=None, # use locally built contract
address=None, # deploy new contract
)
subscriberReceipt = EthContractHelper.DeployContract(
w3=w3,
contract=subscriberContract,
arguments=[ pubSubAddr ],
privKey=privKey,
gas=None, # let web3 estimate
value=0,
confirmPrompt=False # don't prompt for confirmation
)
subscriberAddr = subscriberReceipt.contractAddress

# load deployed Subscriber contract
subscriberContract = EthContractHelper.LoadContract(
w3=w3,
projConf=PROJECT_CONFIG_PATH,
contractName='HelloWorldSubscriber',
release=None, # use locally built contract
address=subscriberAddr, # use deployed contract
)

# subscribe
print('Subscribing...')
subTxReceipt = EthContractHelper.CallContractFunc(
w3=w3,
contract=subscriberContract,
funcName='subscribe',
arguments=[ publisherAddr ],
privKey=privKey,
gas=None, # let web3 estimate
value=10000000000000000, # 0.01 ether
confirmPrompt=False # don't prompt for confirmation
)
print('Subscriber@{} subscribed to publisher@{}'.format(
subscriberAddr,
publisherAddr
))

costs.append(subTxReceipt.gasUsed)
print('Gas used: {}'.format(subTxReceipt.gasUsed))

# record gas used
subscribeCost.append((
numPublishers,
sum(costs) / len(costs), # average gas cost
))

return subscribeCost


def DrawGraph(
dest: os.PathLike,
data: List[Tuple[int, int]],
scaleBy: int,
title: str,
xlabel: str = 'Number of subscribers',
ylabel: str = 'Gas cost',
) -> None:

scale = np.power(10, scaleBy)
axes = plt.subplot()
axes.plot(
np.arange(1, len(data) + 1),
np.array([ cost for _, cost in data ]) / scale,
)

# set y-axis limits
dataAvg = sum([ cost for _, cost in data ])
dataAvg = dataAvg / len(data)
ymax = (dataAvg + (dataAvg * 0.0001)) / scale
ymin = (dataAvg - (dataAvg * 0.0001)) / scale
axes.set_ylim([ymin, ymax])

# avoid scientific notation
current_values = axes.get_yticks()
axes.set_yticklabels(
['{:.04f}'.format(x) for x in current_values]
)

plt.xticks(np.arange(1, len(data) + 1))
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel + f' (1e{scaleBy})')
plt.savefig(dest)


def StopGanache(ganacheProc: subprocess.Popen) -> None:
print('Shutting down ganache (it may take ~15 seconds)...')
waitEnd = time.time() + 20
ganacheProc.terminate()
while ganacheProc.poll() is None:
try:
if time.time() > waitEnd:
print('Force to shut down ganache')
ganacheProc.kill()
else:
print('Still waiting for ganache to shut down...')
ganacheProc.send_signal(signal.SIGINT)
ganacheProc.wait(timeout=2)
except subprocess.TimeoutExpired:
continue
print('Ganache has been shut down')


def main():
ganacheProc = StartGanache()

try:
subscribeCost = RunTests()

print('Subscribe gas cost results:')
for cost in subscribeCost:
print('{:03} publishers: {:010.2f} gas'.format(cost[0], cost[1]))

DrawGraph(
dest=os.path.join(BUILD_DIR_PATH, 'subscribe_gas_cost.svg'),
data=subscribeCost,
scaleBy=5,
title='Gas cost of subscribing',
)
finally:
# finish and exit
StopGanache(ganacheProc)

if __name__ == "__main__":
main()
Loading

0 comments on commit 6ce802c

Please sign in to comment.