@ -0,0 +1,3 @@ | |||||
* @melekes @Greg-Szabo | |||||
*.md @zramsay | |||||
*.rst @zramsay |
@ -0,0 +1,3 @@ | |||||
# tools | |||||
Tools for working with tendermint and associated technologies. Documentation can be found in the `README.md` of each the `tm-bench/` and `tm-monitor/` directories. |
@ -0,0 +1,4 @@ | |||||
BUILD | |||||
RPMS | |||||
SPECS | |||||
tmp |
@ -0,0 +1,204 @@ | |||||
Tendermint Core | |||||
License: Apache2.0 | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
http://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
APPENDIX: How to apply the Apache License to your work. | |||||
To apply the Apache License to your work, attach the following | |||||
boilerplate notice, with the fields enclosed by brackets "{}" | |||||
replaced with your own identifying information. (Don't include | |||||
the brackets!) The text should be enclosed in the appropriate | |||||
comment syntax for the file format. We also recommend that a | |||||
file or class name and description of purpose be included on the | |||||
same "printed page" as the copyright notice for easier | |||||
identification within third-party archives. | |||||
Copyright 2016 All in Bits, Inc | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,289 @@ | |||||
## | |||||
# Extra checks, because we do not use autoconf. | |||||
## | |||||
requirements_check = true | |||||
gpg_check = false | |||||
go_min_version = 1.9.4 | |||||
gpg_key = 2122CBE9 | |||||
ifeq ($(requirements_check),true) | |||||
ifndef GOPATH | |||||
$(error GOPATH not set) | |||||
else | |||||
go_version := $(shell go version | sed "s/^.* go\([0-9\.]*\) .*$$/\1/" ) | |||||
$(info Found go version $(go_version)) | |||||
go_version_check := $(shell echo -e "$(go_min_version)\n$(go_version)" | sort -V | head -1) | |||||
ifneq ($(go_min_version),$(go_version_check)) | |||||
$(error go version go_min_version or above is required) | |||||
endif | |||||
endif | |||||
ifeq ($(gpg_check),true) | |||||
gpg_check := $(shell gpg -K | grep '/$(gpg_key) ' | sed 's,^.*/\($(gpg_key)\) .*$$,\1,') | |||||
ifneq ($(gpg_check),$(gpg_key)) | |||||
$(error GPG key $(gpg_key) not found.) | |||||
else | |||||
$(info GPG key $(gpg_key) found) | |||||
endif | |||||
ifndef GPG_PASSPHRASE | |||||
$(error GPG_PASSPHRASE not set) | |||||
endif | |||||
endif | |||||
endif | |||||
### | |||||
# Here comes the real deal | |||||
### | |||||
binaries = tendermint basecoind ethermint gaia | |||||
build-binaries = build-tendermint build-basecoind build-ethermint build-gaia | |||||
package-rpm = package-rpm-tendermint package-rpm-basecoind package-rpm-ethermint package-rpm-gaia | |||||
install-rpm = install-rpm-tendermint install-rpm-basecoind install-rpm-ethermint install-rpm-gaia | |||||
package-deb = package-deb-tendermint package-deb-basecoind package-deb-ethermint package-deb-gaia | |||||
install-deb = install-deb-tendermint install-deb-basecoind install-deb-ethermint install-deb-gaia | |||||
all: $(binaries) | |||||
build: $(build-binaries) | |||||
package: $(package-rpm) $(package-deb) | |||||
install: $(install-rpm) $(install-deb) | |||||
$(binaries): %: build-% package-rpm-% package-deb-% | |||||
### | |||||
# Build the binaries | |||||
### | |||||
git-branch: | |||||
$(eval GIT_BRANCH=$(shell echo $${GIT_BRANCH:-master})) | |||||
gopath-setup: | |||||
test -d $(GOPATH) || mkdir -p $(GOPATH) | |||||
test -d $(GOPATH)/bin || mkdir -p $(GOPATH)/bin | |||||
test -d $(GOPATH)/src || mkdir -p $(GOPATH)/src | |||||
build-tendermint: git-branch gopath-setup | |||||
@echo "*** Building tendermint" | |||||
go get -d -u github.com/tendermint/tendermint/cmd/tendermint | |||||
cd $(GOPATH)/src/github.com/tendermint/tendermint && git checkout "$(GIT_BRANCH)" && git pull | |||||
export PATH=$(GOPATH)/bin:$(PATH) && $(MAKE) -C $(GOPATH)/src/github.com/tendermint/tendermint get_tools get_vendor_deps build | |||||
cp $(GOPATH)/src/github.com/tendermint/tendermint/build/tendermint $(GOPATH)/bin | |||||
@echo "*** Built tendermint" | |||||
build-ethermint: git-branch gopath-setup | |||||
@echo "*** Building ethermint" | |||||
go get -d -u github.com/tendermint/ethermint/cmd/ethermint | |||||
cd $(GOPATH)/src/github.com/tendermint/ethermint && git checkout "$(GIT_BRANCH)" && git pull | |||||
export PATH=$(GOPATH)/bin:$(PATH) && $(MAKE) -C $(GOPATH)/src/github.com/tendermint/ethermint get_vendor_deps build | |||||
cp $(GOPATH)/src/github.com/tendermint/ethermint/build/ethermint $(GOPATH)/bin | |||||
@echo "*** Built ethermint" | |||||
build-gaia: git-branch gopath-setup | |||||
@echo "*** Building gaia" | |||||
go get -d -u go github.com/cosmos/gaia || echo "Workaround for go downloads." | |||||
cd $(GOPATH)/src/github.com/cosmos/gaia && git checkout "$(GIT_BRANCH)" && git pull | |||||
export PATH=$(GOPATH)/bin:$(PATH) && $(MAKE) -C $(GOPATH)/src/github.com/cosmos/gaia get_vendor_deps install | |||||
@echo "*** Built gaia" | |||||
build-basecoind: git-branch gopath-setup | |||||
@echo "*** Building basecoind from cosmos-sdk" | |||||
go get -d -u github.com/cosmos/cosmos-sdk/examples/basecoin/cmd/basecoind | |||||
cd $(GOPATH)/src/github.com/cosmos/cosmos-sdk && git checkout "$(GIT_BRANCH)" && git pull | |||||
export PATH=$(GOPATH)/bin:$(PATH) && $(MAKE) -C $(GOPATH)/src/github.com/cosmos/cosmos-sdk get_tools get_vendor_deps build | |||||
cp $(GOPATH)/src/github.com/cosmos/cosmos-sdk/build/basecoind $(GOPATH)/bin/basecoind | |||||
@echo "*** Built basecoind from cosmos-sdk" | |||||
### | |||||
# Prepare package files | |||||
### | |||||
# set app_version | |||||
version-%: | |||||
@echo "Checking if binary exists" | |||||
test -f $(GOPATH)/bin/$* | |||||
@echo "BUILD_NUMBER is $(BUILD_NUMBER)" | |||||
test -n "$(BUILD_NUMBER)" | |||||
$(eval $*_version=$(shell $(GOPATH)/bin/$* version | head -1 | cut -d- -f1 | sed 's/^\(ethermint:\s*\|\)\(v\|\)//' | tr -d '\t ' )) | |||||
# set build_folder | |||||
folder-%: version-% | |||||
$(eval build_folder=BUILD/$*-$($*_version)-$(BUILD_NUMBER)) | |||||
# clean up folder structure for package files | |||||
prepare-files = rm -rf $(build_folder) && mkdir -p $(build_folder) && cp -r ./$(1)/* $(build_folder) && mkdir -p $(build_folder)/usr/bin && cp $(GOPATH)/bin/$(1) $(build_folder)/usr/bin | |||||
## | |||||
## Package customizations for the different applications | |||||
## | |||||
prepare-tendermint = | |||||
prepare-ethermint = mkdir -p $(build_folder)/etc/ethermint && \ | |||||
cp $(GOPATH)/src/github.com/tendermint/ethermint/setup/genesis.json $(build_folder)/etc/ethermint/genesis.json && \ | |||||
cp -r $(GOPATH)/src/github.com/tendermint/ethermint/setup/keystore $(build_folder)/etc/ethermint | |||||
prepare-gaia = | |||||
prepare-basecoind = cp $(GOPATH)/bin/basecoind $(build_folder)/usr/bin | |||||
### | |||||
# Package the binary for CentOS/RedHat (RPM) and Debian/Ubuntu (DEB) | |||||
### | |||||
# Depends on rpmbuild, sorry, this can only be built on CentOS/RedHat machines. | |||||
package-rpm-%: folder-% | |||||
@echo "*** Packaging RPM $* version $($*_version)" | |||||
$(call prepare-files,$*) | |||||
$(call prepare-$*) | |||||
rm -rf $(build_folder)/DEBIAN | |||||
mkdir -p $(build_folder)/usr/share/licenses/$* | |||||
cp ./LICENSE $(build_folder)/usr/share/licenses/$*/LICENSE | |||||
chmod -Rf a+rX,u+w,g-w,o-w $(build_folder) | |||||
mkdir -p {SPECS,tmp} | |||||
./generate-spec $* spectemplates SPECS | |||||
sed -i "s/@VERSION@/$($*_version)/" SPECS/$*.spec | |||||
sed -i "s/@BUILD_NUMBER@/$(BUILD_NUMBER)/" SPECS/$*.spec | |||||
sed -i "s/@PACKAGE_NAME@/$*/" SPECS/$*.spec | |||||
rpmbuild -bb SPECS/$*.spec --define "_topdir `pwd`" --define "_tmppath `pwd`/tmp" | |||||
./sign RPMS/x86_64/$*-$($*_version)-$(BUILD_NUMBER).x86_64.rpm "$(gpg_key)" "`which gpg`" | |||||
rpm -Kv RPMS/x86_64/$*-$($*_version)-$(BUILD_NUMBER).x86_64.rpm || echo "rpm returns non-zero exist for some reason. ($?)" | |||||
@echo "*** Packaged RPM $* version $($*_version)" | |||||
package-deb-%: folder-% | |||||
@echo "*** Packaging DEB $* version $($*_version)-$(BUILD_NUMBER)" | |||||
$(call prepare-files,$*) | |||||
$(call prepare-$*) | |||||
mkdir -p $(build_folder)/usr/share/doc/$* | |||||
cp $(build_folder)/DEBIAN/copyright $(build_folder)/usr/share/doc/$* | |||||
chmod -Rf a+rX,u+w,g-w,o-w $(build_folder) | |||||
sed -i "s/@VERSION@/$($*_version)-$(BUILD_NUMBER)/" $(build_folder)/DEBIAN/changelog | |||||
sed -i "s/@STABILITY@/stable/" $(build_folder)/DEBIAN/changelog | |||||
sed -i "s/@DATETIMESTAMP@/`date +%a,\ %d\ %b\ %Y\ %T\ %z`/" $(build_folder)/DEBIAN/changelog | |||||
sed -i "s/@VERSION@/$($*_version)-$(BUILD_NUMBER)/" $(build_folder)/DEBIAN/control | |||||
gzip -c $(build_folder)/DEBIAN/changelog > $(build_folder)/usr/share/doc/$*/changelog.Debian.gz | |||||
gzip -c $(build_folder)/DEBIAN/changelog > $(build_folder)/usr/share/doc/$*/changelog.Debian.amd64.gz | |||||
sed -i "s/@INSTALLEDSIZE@/`du -ks $(build_folder) | cut -f 1`/" $(build_folder)/DEBIAN/control | |||||
cd $(build_folder) && tar --owner=root --group=root -cvJf ../../tmp/data.tar.xz --exclude DEBIAN * | |||||
cd $(build_folder)/DEBIAN && tar --owner=root --group=root -cvzf ../../../tmp/control.tar.gz * | |||||
echo "2.0" > tmp/debian-binary | |||||
cp ./_gpg tmp/ | |||||
cd tmp && sed -i "s/@DATETIMESTAMP@/`date +%a\ %b\ %d\ %T\ %Y`/" _gpg | |||||
cd tmp && sed -i "s/@BINMD5@/`md5sum debian-binary | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@BINSHA1@/`sha1sum debian-binary | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@BINSIZE@/`stat -c %s debian-binary | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@CONMD5@/`md5sum control.tar.gz | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@CONSHA1@/`sha1sum control.tar.gz | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@CONSIZE@/`stat -c %s control.tar.gz | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@DATMD5@/`md5sum data.tar.xz | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@DATSHA1@/`sha1sum data.tar.xz | cut -d\ -f1`/" _gpg | |||||
cd tmp && sed -i "s/@DATSIZE@/`stat -c %s data.tar.xz | cut -d\ -f1`/" _gpg | |||||
gpg --batch --passphrase "$(GPG_PASSPHRASE)" --clearsign tmp/_gpg | |||||
mv tmp/_gpg.asc tmp/_gpgbuilder | |||||
ar r tmp/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb tmp/debian-binary tmp/control.tar.gz tmp/data.tar.xz tmp/_gpgbuilder | |||||
mv tmp/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb RPMS/ | |||||
rm tmp/debian-binary tmp/control.tar.gz tmp/data.tar.xz tmp/_gpgbuilder tmp/_gpg | |||||
@echo "*** Packaged DEB $* version $($*_version)-$(BUILD_NUMBER)" | |||||
install-rpm-%: version-% | |||||
#Make sure your host has the IAM role to read/write the S3 bucket OR that you set up ~/.boto | |||||
@echo "*** Uploading $*-$($*_version)-$(BUILD_NUMBER).x86_64.rpm to AWS $(DEVOPS_PATH)CentOS repository" | |||||
aws s3 sync s3://tendermint-packages/$(DEVOPS_PATH)centos/ tmp/s3/ --delete | |||||
mkdir -p tmp/s3/7/os/x86_64/Packages | |||||
cp RPMS/x86_64/$*-$($*_version)-$(BUILD_NUMBER).x86_64.rpm tmp/s3/7/os/x86_64/Packages | |||||
cp ./RPM-GPG-KEY-Tendermint tmp/s3/7/os/x86_64/ | |||||
cp ./tendermint.repo tmp/s3/7/os/x86_64/ | |||||
rm -f tmp/s3/7/os/x86_64/repodata/*.bz2 tmp/s3/7/os/x86_64/repodata/*.gz tmp/s3/7/os/x86_64/repodata/repomd.xml.asc | |||||
createrepo tmp/s3/7/os/x86_64/Packages -u https://tendermint-packages.interblock.io/$(DEVOPS_PATH)centos/7/os/x86_64/Packages -o tmp/s3/7/os/x86_64 --update -S --repo Tendermint --content tendermint --content basecoind --content ethermint | |||||
gpg --batch --passphrase "$(GPG_PASSPHRASE)" --detach-sign -a tmp/s3/7/os/x86_64/repodata/repomd.xml | |||||
aws s3 sync tmp/s3/ s3://tendermint-packages/$(DEVOPS_PATH)centos/ --delete --acl public-read | |||||
@echo "*** Uploaded $* to AWS $(DEVOPS_PATH)CentOS repository" | |||||
install-deb-%: version-% | |||||
@echo "*** Uploading $*-$($*_version)-$(BUILD_NUMBER)_amd64.deb to AWS $(DEVOPS_PATH)Debian repository" | |||||
@echo "Testing if $*-$($*_version)-$(BUILD_NUMBER)_amd64.deb is already uploaded" | |||||
test ! -f tmp/debian-s3/pool/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb | |||||
aws s3 sync s3://tendermint-packages/$(DEVOPS_PATH)debian/ tmp/debian-s3/ --delete | |||||
@echo "Testing if $*-$($*_version)-$(BUILD_NUMBER)_amd64.deb is already uploaded" | |||||
test ! -f tmp/debian-s3/pool/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb | |||||
cp ./tendermint.list tmp/debian-s3/ | |||||
mkdir -p tmp/debian-s3/pool tmp/debian-s3/dists/stable/main/binary-amd64 | |||||
cp RPMS/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb tmp/debian-s3/pool | |||||
cp ./Release_amd64 tmp/debian-s3/dists/stable/main/binary-amd64/Release | |||||
#Packages / Packages.gz | |||||
echo > tmp/Package | |||||
echo "Filename: pool/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb" >> tmp/Package | |||||
echo "MD5sum: `md5sum RPMS/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb | cut -d\ -f 1`" >> tmp/Package | |||||
echo "SHA1: `sha1sum RPMS/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb | cut -d\ -f 1`" >> tmp/Package | |||||
echo "SHA256: `sha256sum RPMS/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb | cut -d\ -f 1`" >> tmp/Package | |||||
echo "Size: `stat -c %s RPMS/$*-$($*_version)-$(BUILD_NUMBER)_amd64.deb | cut -d\ -f 1`" >> tmp/Package | |||||
cat BUILD/$*-$($*_version)-$(BUILD_NUMBER)/DEBIAN/control >> tmp/Package | |||||
cat tmp/Package >> tmp/debian-s3/dists/stable/main/binary-amd64/Packages | |||||
rm -f tmp/debian-s3/dists/stable/main/binary-amd64/Packages.gz | |||||
gzip -c tmp/debian-s3/dists/stable/main/binary-amd64/Packages > tmp/debian-s3/dists/stable/main/binary-amd64/Packages.gz | |||||
rm -f tmp/Package | |||||
#main / Release / InRelease / Release.gpg | |||||
cp ./Release tmp/debian-s3/dists/stable/main/Release | |||||
rm -f tmp/debian-s3/dists/stable/main/InRelease | |||||
rm -f tmp/debian-s3/dists/stable/main/Release.gpg | |||||
echo "MD5Sum:" >> tmp/debian-s3/dists/stable/main/Release | |||||
cd tmp/debian-s3/dists/stable/main && for f in `find . -type f | sed 's/^.\///'` ; do test "$$f" == "Release" && continue ; echo -n " " ; md5sum $$f | sed "s/ / `stat -c %s $$f` /" ; done >> Release | |||||
echo "SHA1:" >> tmp/debian-s3/dists/stable/main/Release | |||||
cd tmp/debian-s3/dists/stable/main && for f in `find . -type f | sed 's/^.\///'` ; do test "$$f" == "Release" && continue ; echo -n " " ; sha1sum $$f | sed "s/ / `stat -c %s $$f` /" ; done >> Release | |||||
echo "SHA256:" >> tmp/debian-s3/dists/stable/main/Release | |||||
cd tmp/debian-s3/dists/stable/main && for f in `find . -type f | sed 's/^.\///'` ; do test "$$f" == "Release" && continue ; echo -n " " ; sha256sum $$f | sed "s/ / `stat -c %s $$f` /" ; done >> Release | |||||
gpg --batch --passphrase "$(GPG_PASSPHRASE)" --digest-algo SHA256 -b -a tmp/debian-s3/dists/stable/main/Release | |||||
mv tmp/debian-s3/dists/stable/main/Release.asc tmp/debian-s3/dists/stable/main/Release.gpg | |||||
gpg --batch --passphrase "$(GPG_PASSPHRASE)" --digest-algo SHA512 --clearsign tmp/debian-s3/dists/stable/main/Release | |||||
mv tmp/debian-s3/dists/stable/main/Release.asc tmp/debian-s3/dists/stable/main/InRelease | |||||
#stable / Release / InRelease / Release.gpg | |||||
cp ./Release tmp/debian-s3/dists/stable/Release | |||||
rm -f tmp/debian-s3/dists/stable/InRelease | |||||
rm -f tmp/debian-s3/dists/stable/Release.gpg | |||||
echo "MD5Sum:" >> tmp/debian-s3/dists/stable/Release | |||||
cd tmp/debian-s3/dists/stable && for f in `find . -type f | sed 's/^.\///'` ; do test "$$f" == "Release" && continue ; echo -n " " ; md5sum $$f | sed "s/ / `stat -c %s $$f` /" ; done >> Release | |||||
echo "SHA1:" >> tmp/debian-s3/dists/stable/Release | |||||
cd tmp/debian-s3/dists/stable && for f in `find . -type f | sed 's/^.\///'` ; do test "$$f" == "Release" && continue ; echo -n " " ; sha1sum $$f | sed "s/ / `stat -c %s $$f` /" ; done >> Release | |||||
echo "SHA256:" >> tmp/debian-s3/dists/stable/Release | |||||
cd tmp/debian-s3/dists/stable && for f in `find . -type f | sed 's/^.\///'` ; do test "$$f" == "Release" && continue ; echo -n " " ; sha256sum $$f | sed "s/ / `stat -c %s $$f` /" ; done >> Release | |||||
gpg --batch --passphrase "$(GPG_PASSPHRASE)" --digest-algo SHA256 -b -a tmp/debian-s3/dists/stable/Release | |||||
mv tmp/debian-s3/dists/stable/Release.asc tmp/debian-s3/dists/stable/Release.gpg | |||||
gpg --batch --passphrase "$(GPG_PASSPHRASE)" --digest-algo SHA512 --clearsign tmp/debian-s3/dists/stable/Release | |||||
mv tmp/debian-s3/dists/stable/Release.asc tmp/debian-s3/dists/stable/InRelease | |||||
aws s3 sync tmp/debian-s3/ s3://tendermint-packages/$(DEVOPS_PATH)debian/ --delete --acl public-read | |||||
@echo "*** Uploaded $*-$($*_version)-$(BUILD_NUMBER)_amd64.deb to AWS $(DEVOPS_PATH)Debian repository" | |||||
mostlyclean: | |||||
rm -rf {BUILDROOT,SOURCES,SPECS,SRPMS,tmp} | |||||
clean: mostlyclean | |||||
rm -rf {BUILD,RPMS} | |||||
distclean: clean | |||||
rm -rf $(GOPATH)/src/github.com/tendermint/tendermint | |||||
rm -rf $(GOPATH)/src/github.com/cosmos/cosmos-sdk | |||||
rm -rf $(GOPATH)/src/github.com/tendermint/ethermint | |||||
rm -rf $(GOPATH)/bin/tendermint | |||||
rm -rf $(GOPATH)/bin/basecoind | |||||
rm -rf $(GOPATH)/bin/ethermint | |||||
rm -rf $(GOPATH)/bin/gaia | |||||
.PHONY : clean | |||||
@ -0,0 +1,19 @@ | |||||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||||
Version: GnuPG v2.0.22 (GNU/Linux) | |||||
mQENBFk97ngBCADaiPQFKJI7zWYdUKqC490DzY9g9LatsWoJErK5LuMXwEnF5i+a | |||||
UkygueukA4C5U7L71l5EeOB9rtb6AbkF4IEZsmmp93APec/3Vfbac9xvK4dBdiht | |||||
F8SrazPdHeR6AKcZH8ZpG/+mdONvGb/gEgtxVjaeIJFpCbjKLlKEXazh2zamhhth | |||||
q+Nn/17QmI3KBiaGqQK5w4kGZ4mZPy6fXMQhW5dDMq9f4anlGIAYi9O53dVxsx2S | |||||
5d+NHuGer5Ps0u6WMJi/e+UT2EGwzP6ygOxkIjyhMFuVftabOtSSrRHHetw8UAaI | |||||
N/RPn2gSbQtOQ7unzHDXp3/o6/r2nDEErPyJABEBAAG0LkdyZWcgU3phYm8gKFRl | |||||
bmRlcm1pbnQpIDxncmVnQHBoaWxvc29iZWFyLmNvbT6JATkEEwECACMFAlk97ngC | |||||
GwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRDIkIHIISLL6bX/CACXTKmO | |||||
u5XgvJICH0pHNeVS5/4Om1Rsg1xNmEkGFBP8N2fqn576exbOLgWLSyNHTEyrJNoc | |||||
iTeUtod2qqbVGwRgWm1zeiP8NBYiQ9SUbqskIqcPavJNGWIxsCB0p/odoZah8xSj | |||||
tGrkoyoxrc+7z2JgKYK8SVSkJXQkzuc5/ZlY85ci5gPKQhlo5YDqGo+4U9n/Ieo5 | |||||
nkF8LBalFC2j7A7sQNroEicpulpGhIq3jyUHtadX01z3pNzuX+wfHX9futoet0YS | |||||
tG2007WoPGV0whGnoKxmk0JhwzhscC2XNtJl1GZcwqOOlPU9eGtZuPKj/HBAlRtz | |||||
4xTOAcklpg8soqRA | |||||
=jNDW | |||||
-----END PGP PUBLIC KEY BLOCK----- |
@ -0,0 +1,7 @@ | |||||
Origin: Tendermint | |||||
Label: Tendermint | |||||
Suite: stable | |||||
Date: Fri, 16 Jun 2017 19:44:00 UTC | |||||
Architectures: amd64 | |||||
Components: main | |||||
Description: Tendermint repository |
@ -0,0 +1,5 @@ | |||||
Archive: stable | |||||
Component: main | |||||
Origin: Tendermint | |||||
Label: Tendermint | |||||
Architecture: amd64 |
@ -0,0 +1,8 @@ | |||||
Version: 4 | |||||
Signer: | |||||
Date: @DATETIMESTAMP@ | |||||
Role: builder | |||||
Files: | |||||
@BINMD5@ @BINSHA1@ @BINSIZE@ debian-binary | |||||
@CONMD5@ @CONSHA1@ @CONSIZE@ control.tar.gz | |||||
@DATMD5@ @DATSHA1@ @DATSIZE@ data.tar.xz |
@ -0,0 +1,6 @@ | |||||
basecoind (@VERSION@) @STABILITY@; urgency=medium | |||||
* Automatic build. See https://github.com/cosmos/cosmos-sdk for more information. | |||||
-- Greg Szabo <greg@philosobear.com> @DATETIMESTAMP@ | |||||
@ -0,0 +1 @@ | |||||
9 |
@ -0,0 +1,14 @@ | |||||
Source: basecoind | |||||
Section: net | |||||
Priority: optional | |||||
Maintainer: Greg Szabo <greg@philosobear.com> | |||||
Build-Depends: debhelper (>=9) | |||||
Standards-Version: 3.9.6 | |||||
Homepage: https://tendermint.com | |||||
Package: basecoind | |||||
Architecture: amd64 | |||||
Version: @VERSION@ | |||||
Installed-Size: @INSTALLEDSIZE@ | |||||
Description: basecoind is a Proof-of-Stake cryptocurrency and framework | |||||
Basecoind is an ABCI application designed to be used with the Tendermint consensus engine to form a Proof-of-Stake cryptocurrency. It also provides a general purpose framework for extending the feature-set of the cryptocurrency by implementing plugins. | |||||
@ -0,0 +1,21 @@ | |||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | |||||
Upstream-Name: basecoind | |||||
Source: https://github.com/cosmos/cosmos-sdk | |||||
Files: * | |||||
Copyright: 2017 All In Bits, Inc. | |||||
License: Apache-2.0 | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
. | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
. | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. | |||||
. | |||||
On Debian systems, the full text of the Apache License 2.0 can be found | |||||
in the file `/usr/share/common-licenses/Apache-2.0'. |
@ -0,0 +1,41 @@ | |||||
#!/bin/sh | |||||
# postinst script for basecoind | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <postinst> `configure' <most-recently-configured-version> | |||||
# * <old-postinst> `abort-upgrade' <new version> | |||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package> | |||||
# <new-version> | |||||
# * <postinst> `abort-remove' | |||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' | |||||
# <failed-install-package> <version> `removing' | |||||
# <conflicting-package> <version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
configure) | |||||
chown basecoind.basecoind /etc/basecoind | |||||
sudo -Hu basecoind basecoind node init --home /etc/basecoind 2B24DEE2364762300168DF19B6C18BCE2D399EA2 | |||||
systemctl daemon-reload | |||||
;; | |||||
abort-upgrade|abort-remove|abort-deconfigure) | |||||
;; | |||||
*) | |||||
echo "postinst called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,41 @@ | |||||
#!/bin/sh | |||||
# postrm script for basecoin | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <postrm> `remove' | |||||
# * <postrm> `purge' | |||||
# * <old-postrm> `upgrade' <new-version> | |||||
# * <new-postrm> `failed-upgrade' <old-version> | |||||
# * <new-postrm> `abort-install' | |||||
# * <new-postrm> `abort-install' <old-version> | |||||
# * <new-postrm> `abort-upgrade' <old-version> | |||||
# * <disappearer's-postrm> `disappear' <overwriter> | |||||
# <overwriter-version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
upgrade|failed-upgrade|abort-upgrade) | |||||
systemctl daemon-reload | |||||
;; | |||||
purge|remove|abort-install|disappear) | |||||
systemctl daemon-reload | |||||
;; | |||||
*) | |||||
echo "postrm called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,38 @@ | |||||
#!/bin/sh | |||||
# preinst script for basecoind | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <new-preinst> `install' | |||||
# * <new-preinst> `install' <old-version> | |||||
# * <new-preinst> `upgrade' <old-version> | |||||
# * <old-preinst> `abort-upgrade' <new-version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
install|upgrade) | |||||
if ! grep -q '^basecoind:' /etc/passwd ; then | |||||
useradd -k /dev/null -r -m -b /etc basecoind | |||||
chmod 755 /etc/basecoind | |||||
fi | |||||
;; | |||||
abort-upgrade) | |||||
;; | |||||
*) | |||||
echo "preinst called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,38 @@ | |||||
#!/bin/sh | |||||
# prerm script for basecoin | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <prerm> `remove' | |||||
# * <old-prerm> `upgrade' <new-version> | |||||
# * <new-prerm> `failed-upgrade' <old-version> | |||||
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> | |||||
# * <deconfigured's-prerm> `deconfigure' `in-favour' | |||||
# <package-being-installed> <version> `removing' | |||||
# <conflicting-package> <version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
remove|upgrade|deconfigure) | |||||
systemctl stop basecoind 2> /dev/null || : | |||||
;; | |||||
failed-upgrade) | |||||
;; | |||||
*) | |||||
echo "prerm called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,2 @@ | |||||
disable basecoind.service | |||||
@ -0,0 +1,18 @@ | |||||
[Unit] | |||||
Description=Basecoind | |||||
Requires=network-online.target | |||||
After=network-online.target | |||||
[Service] | |||||
Environment="BCHOME=/etc/basecoind" | |||||
Restart=on-failure | |||||
User=basecoind | |||||
Group=basecoind | |||||
PermissionsStartOnly=true | |||||
ExecStart=/usr/bin/basecoind start | |||||
ExecReload=/bin/kill -HUP $MAINPID | |||||
KillSignal=SIGTERM | |||||
[Install] | |||||
WantedBy=multi-user.target | |||||
@ -0,0 +1,12 @@ | |||||
{ | |||||
"address": "1B1BE55F969F54064628A63B9559E7C21C925165", | |||||
"priv_key": { | |||||
"type": "ed25519", | |||||
"data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" | |||||
}, | |||||
"pub_key": { | |||||
"type": "ed25519", | |||||
"data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" | |||||
} | |||||
} | |||||
@ -0,0 +1,12 @@ | |||||
{ | |||||
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", | |||||
"priv_key": { | |||||
"type": "ed25519", | |||||
"data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" | |||||
}, | |||||
"pub_key": { | |||||
"type": "ed25519", | |||||
"data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" | |||||
} | |||||
} | |||||
@ -0,0 +1,6 @@ | |||||
ethermint (@VERSION@) @STABILITY@; urgency=medium | |||||
* Automatic build. See https://github.com/tendermint/tendermint for more information. | |||||
-- Greg Szabo <greg@philosobear.com> @DATETIMESTAMP@ | |||||
@ -0,0 +1 @@ | |||||
9 |
@ -0,0 +1,15 @@ | |||||
Source: ethermint | |||||
Section: net | |||||
Priority: optional | |||||
Maintainer: Greg Szabo <greg@philosobear.com> | |||||
Build-Depends: debhelper (>=9) | |||||
Depends: tendermint (>=0.11.0) | |||||
Standards-Version: 3.9.6 | |||||
Homepage: https://tendermint.com | |||||
Package: ethermint | |||||
Architecture: amd64 | |||||
Version: @VERSION@ | |||||
Installed-Size: @INSTALLEDSIZE@ | |||||
Description: ethermint enables ethereum as an ABCI application on tendermint and the COSMOS hub | |||||
Ethermint enables ethereum to run as an ABCI application on tendermint and the COSMOS hub. This application allows you to get all the benefits of ethereum without having to run your own miners. | |||||
@ -0,0 +1,21 @@ | |||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | |||||
Upstream-Name: ethermint | |||||
Source: https://github.com/tendermint/ethermint | |||||
Files: * | |||||
Copyright: 2017 All In Bits, Inc. | |||||
License: Apache-2.0 | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
. | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
. | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. | |||||
. | |||||
On Debian systems, the full text of the Apache License 2.0 can be found | |||||
in the file `/usr/share/common-licenses/Apache-2.0'. |
@ -0,0 +1,46 @@ | |||||
#!/bin/sh | |||||
# postinst script for ethermint | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <postinst> `configure' <most-recently-configured-version> | |||||
# * <old-postinst> `abort-upgrade' <new version> | |||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package> | |||||
# <new-version> | |||||
# * <postinst> `abort-remove' | |||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' | |||||
# <failed-install-package> <version> `removing' | |||||
# <conflicting-package> <version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
configure) | |||||
chown ethermint.ethermint /etc/ethermint | |||||
chown ethermint.ethermint /etc/ethermint/genesis.json | |||||
chown ethermint.ethermint /etc/ethermint/keystore | |||||
chown ethermint.ethermint /etc/ethermint/keystore/UTC--2016-10-21T22-30-03.071787745Z--7eff122b94897ea5b0e2a9abf47b86337fafebdc | |||||
sudo -Hu ethermint /usr/bin/ethermint --datadir /etc/ethermint init /etc/ethermint/genesis.json | |||||
sudo -Hu ethermint tendermint init --home /etc/ethermint | |||||
systemctl daemon-reload | |||||
;; | |||||
abort-upgrade|abort-remove|abort-deconfigure) | |||||
;; | |||||
*) | |||||
echo "postinst called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,41 @@ | |||||
#!/bin/sh | |||||
# postrm script for ethermint | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <postrm> `remove' | |||||
# * <postrm> `purge' | |||||
# * <old-postrm> `upgrade' <new-version> | |||||
# * <new-postrm> `failed-upgrade' <old-version> | |||||
# * <new-postrm> `abort-install' | |||||
# * <new-postrm> `abort-install' <old-version> | |||||
# * <new-postrm> `abort-upgrade' <old-version> | |||||
# * <disappearer's-postrm> `disappear' <overwriter> | |||||
# <overwriter-version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
upgrade|failed-upgrade|abort-upgrade) | |||||
systemctl daemon-reload | |||||
;; | |||||
purge|remove|abort-install|disappear) | |||||
systemctl daemon-reload | |||||
;; | |||||
*) | |||||
echo "postrm called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,38 @@ | |||||
#!/bin/sh | |||||
# preinst script for ethermint | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <new-preinst> `install' | |||||
# * <new-preinst> `install' <old-version> | |||||
# * <new-preinst> `upgrade' <old-version> | |||||
# * <old-preinst> `abort-upgrade' <new-version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
install|upgrade) | |||||
if ! grep -q '^ethermint:' /etc/passwd ; then | |||||
useradd -k /dev/null -r -m -b /etc ethermint | |||||
chmod 755 /etc/ethermint | |||||
fi | |||||
;; | |||||
abort-upgrade) | |||||
;; | |||||
*) | |||||
echo "preinst called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,38 @@ | |||||
#!/bin/sh | |||||
# prerm script for ethermint | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <prerm> `remove' | |||||
# * <old-prerm> `upgrade' <new-version> | |||||
# * <new-prerm> `failed-upgrade' <old-version> | |||||
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> | |||||
# * <deconfigured's-prerm> `deconfigure' `in-favour' | |||||
# <package-being-installed> <version> `removing' | |||||
# <conflicting-package> <version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
remove|upgrade|deconfigure) | |||||
systemctl stop ethermint 2> /dev/null || : | |||||
;; | |||||
failed-upgrade) | |||||
;; | |||||
*) | |||||
echo "prerm called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,2 @@ | |||||
disable ethermint.service | |||||
@ -0,0 +1,17 @@ | |||||
[Unit] | |||||
Description=Ethermint | |||||
Requires=network-online.target | |||||
After=network-online.target | |||||
[Service] | |||||
Restart=on-failure | |||||
User=ethermint | |||||
Group=ethermint | |||||
PermissionsStartOnly=true | |||||
ExecStart=/usr/bin/ethermint --datadir /etc/ethermint | |||||
ExecReload=/bin/kill -HUP $MAINPID | |||||
KillSignal=SIGTERM | |||||
[Install] | |||||
WantedBy=multi-user.target | |||||
@ -0,0 +1,6 @@ | |||||
gaia (@VERSION@) @STABILITY@; urgency=medium | |||||
* Automatic build. See https://github.com/tendermint/basecoin for more information. | |||||
-- Greg Szabo <greg@philosobear.com> @DATETIMESTAMP@ | |||||
@ -0,0 +1 @@ | |||||
9 |
@ -0,0 +1,14 @@ | |||||
Source: gaia | |||||
Section: net | |||||
Priority: optional | |||||
Maintainer: Greg Szabo <greg@philosobear.com> | |||||
Build-Depends: debhelper (>=9) | |||||
Standards-Version: 3.9.6 | |||||
Homepage: https://cosmos.network | |||||
Package: gaia | |||||
Architecture: amd64 | |||||
Version: @VERSION@ | |||||
Installed-Size: @INSTALLEDSIZE@ | |||||
Description: gaia - Tendermint Cosmos delegation game chain | |||||
Gaia description comes later. | |||||
@ -0,0 +1,21 @@ | |||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | |||||
Upstream-Name: gaia | |||||
Source: https://github.com/cosmos/gaia | |||||
Files: * | |||||
Copyright: 2017 All In Bits, Inc. | |||||
License: Apache-2.0 | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
. | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
. | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. | |||||
. | |||||
On Debian systems, the full text of the Apache License 2.0 can be found | |||||
in the file `/usr/share/common-licenses/Apache-2.0'. |
@ -0,0 +1,41 @@ | |||||
#!/bin/sh | |||||
# postinst script for gaia | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <postinst> `configure' <most-recently-configured-version> | |||||
# * <old-postinst> `abort-upgrade' <new version> | |||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package> | |||||
# <new-version> | |||||
# * <postinst> `abort-remove' | |||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' | |||||
# <failed-install-package> <version> `removing' | |||||
# <conflicting-package> <version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
configure) | |||||
chown gaia.gaia /etc/gaia | |||||
sudo -Hu gaia gaia node init --home /etc/gaia 2B24DEE2364762300168DF19B6C18BCE2D399EA2 | |||||
systemctl daemon-reload | |||||
;; | |||||
abort-upgrade|abort-remove|abort-deconfigure) | |||||
;; | |||||
*) | |||||
echo "postinst called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,41 @@ | |||||
#!/bin/sh | |||||
# postrm script for gaia | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <postrm> `remove' | |||||
# * <postrm> `purge' | |||||
# * <old-postrm> `upgrade' <new-version> | |||||
# * <new-postrm> `failed-upgrade' <old-version> | |||||
# * <new-postrm> `abort-install' | |||||
# * <new-postrm> `abort-install' <old-version> | |||||
# * <new-postrm> `abort-upgrade' <old-version> | |||||
# * <disappearer's-postrm> `disappear' <overwriter> | |||||
# <overwriter-version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
upgrade|failed-upgrade|abort-upgrade) | |||||
systemctl daemon-reload | |||||
;; | |||||
purge|remove|abort-install|disappear) | |||||
systemctl daemon-reload | |||||
;; | |||||
*) | |||||
echo "postrm called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,38 @@ | |||||
#!/bin/sh | |||||
# preinst script for gaia | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <new-preinst> `install' | |||||
# * <new-preinst> `install' <old-version> | |||||
# * <new-preinst> `upgrade' <old-version> | |||||
# * <old-preinst> `abort-upgrade' <new-version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
install|upgrade) | |||||
if ! grep -q '^gaia:' /etc/passwd ; then | |||||
useradd -k /dev/null -r -m -b /etc gaia | |||||
chmod 755 /etc/gaia | |||||
fi | |||||
;; | |||||
abort-upgrade) | |||||
;; | |||||
*) | |||||
echo "preinst called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,38 @@ | |||||
#!/bin/sh | |||||
# prerm script for gaia | |||||
# | |||||
set -e | |||||
# summary of how this script can be called: | |||||
# * <prerm> `remove' | |||||
# * <old-prerm> `upgrade' <new-version> | |||||
# * <new-prerm> `failed-upgrade' <old-version> | |||||
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> | |||||
# * <deconfigured's-prerm> `deconfigure' `in-favour' | |||||
# <package-being-installed> <version> `removing' | |||||
# <conflicting-package> <version> | |||||
# for details, see https://www.debian.org/doc/debian-policy/ or | |||||
# the debian-policy package | |||||
case "$1" in | |||||
remove|upgrade|deconfigure) | |||||
systemctl stop gaia 2> /dev/null || : | |||||
;; | |||||
failed-upgrade) | |||||
;; | |||||
*) | |||||
echo "prerm called with unknown argument \`$1'" >&2 | |||||
exit 1 | |||||
;; | |||||
esac | |||||
# dh_installdeb will replace this with shell code automatically | |||||
# generated by other debhelper scripts. | |||||
#DEBHELPER# | |||||
exit 0 |
@ -0,0 +1,2 @@ | |||||
disable gaia.service | |||||
@ -0,0 +1,17 @@ | |||||
[Unit] | |||||
Description=Gaia | |||||
Requires=network-online.target | |||||
After=network-online.target | |||||
[Service] | |||||
Restart=on-failure | |||||
User=gaia | |||||
Group=gaia | |||||
PermissionsStartOnly=true | |||||
ExecStart=/usr/bin/gaia node start --home=/etc/gaia | |||||
ExecReload=/bin/kill -HUP $MAINPID | |||||
KillSignal=SIGTERM | |||||
[Install] | |||||
WantedBy=multi-user.target | |||||
@ -0,0 +1,12 @@ | |||||
{ | |||||
"address": "1B1BE55F969F54064628A63B9559E7C21C925165", | |||||
"priv_key": { | |||||
"type": "ed25519", | |||||
"data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" | |||||
}, | |||||
"pub_key": { | |||||
"type": "ed25519", | |||||
"data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" | |||||
} | |||||
} | |||||
@ -0,0 +1,12 @@ | |||||
{ | |||||
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", | |||||
"priv_key": { | |||||
"type": "ed25519", | |||||
"data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" | |||||
}, | |||||
"pub_key": { | |||||
"type": "ed25519", | |||||
"data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" | |||||
} | |||||
} | |||||
@ -0,0 +1,36 @@ | |||||
#!/bin/bash | |||||
if [ $# -ne 3 ]; then | |||||
echo "Usage: $0 <application> <template_source_dir> <SPEC_dir>" | |||||
exit 1 | |||||
fi | |||||
app=$1 | |||||
src=$2 | |||||
dst=$3 | |||||
# Find spectemplate | |||||
if [ ! -f "$src/$app.spec" ]; then | |||||
if [ ! -f "$src/app-template.spec" ]; then | |||||
echo "Source template not found." | |||||
exit 1 | |||||
else | |||||
srcfile="$src/app-template.spec" | |||||
fi | |||||
else | |||||
srcfile="$src/$app.spec" | |||||
fi | |||||
# Copy spectemplate to SPECS | |||||
cp "$srcfile" "$dst/$app.spec" | |||||
# Apply any variables defined in .data | |||||
if [ -f "$src/$app.data" ]; then | |||||
srcdata="$src/$app.data" | |||||
source "$srcdata" | |||||
for var in `grep -v -e ^# -e ^\s*$ "$srcdata" | grep = | sed 's/\s*=.*$//'` | |||||
do | |||||
sed -i "s\\@${var}@\\${!var}\\g" "$dst/$app.spec" | |||||
done | |||||
fi | |||||
@ -0,0 +1,26 @@ | |||||
#!/usr/bin/expect -f | |||||
set timeout 3 | |||||
set PACKAGE [lindex $argv 0] | |||||
set GPG_NAME [lindex $argv 1] | |||||
set GPG_PATH [lindex $argv 2] | |||||
set GPG_PASSPHRASE $env(GPG_PASSPHRASE) | |||||
if {[llength $argv] == 0} { | |||||
send_user "Usage: ./sign <rpm_package> <gpg_key> <gpg_binary>\n" | |||||
exit 1 | |||||
} | |||||
send_user "\nSigning $PACKAGE\n" | |||||
spawn rpmsign --resign $PACKAGE --define "_signature gpg" --define "_gpg_name $GPG_NAME" --define "_gpgbin $GPG_PATH" | |||||
expect { | |||||
timeout { send_user "\nTimeout signing $PACKAGE\n"; exit 1 } | |||||
"Enter pass phrase:" | |||||
} | |||||
send "$GPG_PASSPHRASE\r" | |||||
expect { | |||||
timeout { send_user "\nTimeout signing $PACKAGE\n"; exit 1 } | |||||
"Pass phrase is good." | |||||
} | |||||
interact | |||||
sleep 3 | |||||
@ -0,0 +1,55 @@ | |||||
Version: @VERSION@ | |||||
Release: @BUILD_NUMBER@ | |||||
%define __spec_install_post %{nil} | |||||
%define debug_package %{nil} | |||||
%define __os_install_post %{nil} | |||||
Name: @PACKAGE_NAME@ | |||||
Summary: @PACKAGE_SUMMARY@ | |||||
License: Apache 2.0 | |||||
URL: @PACKAGE_URL@ | |||||
Packager: Greg Szabo | |||||
@PACKAGE_ADDITIONAL_HEADER@ | |||||
%description | |||||
@PACKAGE_DESCRIPTION@ | |||||
%pre | |||||
if ! %{__grep} -q '^%{name}:' /etc/passwd ; then | |||||
useradd -r -b %{_sysconfdir} %{name} | |||||
mkdir -p %{_sysconfdir}/%{name} | |||||
chmod 755 %{_sysconfdir}/%{name} | |||||
chown %{name}.%{name} %{_sysconfdir}/%{name} | |||||
fi | |||||
%prep | |||||
# Nothing to do here. - It is done in the Makefile. | |||||
%build | |||||
# Nothing to do here. | |||||
%install | |||||
cd %{name}-%{version}-%{release} | |||||
%{__cp} -a * %{buildroot} | |||||
%post | |||||
sudo -Hu %{name} %{name} node init --home %{_sysconfdir}/%{name} 2B24DEE2364762300168DF19B6C18BCE2D399EA2 | |||||
systemctl daemon-reload | |||||
%preun | |||||
systemctl stop %{name} 2> /dev/null || : | |||||
%postun | |||||
systemctl daemon-reload | |||||
%files | |||||
%ghost %attr(0755, %{name}, %{name}) %dir %{_sysconfdir}/%{name} | |||||
%{_bindir}/* | |||||
%{_sysconfdir}/systemd/system/* | |||||
%{_sysconfdir}/systemd/system-preset/* | |||||
%dir %{_datadir}/%{name} | |||||
%{_datadir}/%{name}/* | |||||
%dir %{_defaultlicensedir}/%{name} | |||||
%doc %{_defaultlicensedir}/%{name}/LICENSE | |||||
@ -0,0 +1,5 @@ | |||||
PACKAGE_SUMMARY="basecoind is a Proof-of-Stake cryptocurrency and framework" | |||||
PACKAGE_URL="https://cosmos.network/" | |||||
PACKAGE_ADDITIONAL_HEADER="Provides: basecoind" | |||||
PACKAGE_DESCRIPTION="Basecoind is an ABCI application designed to be used with the Tendermint consensus engine to form a Proof-of-Stake cryptocurrency. It also provides a general purpose framework for extending the feature-set of the cryptocurrency by implementing plugins." | |||||
@ -0,0 +1,5 @@ | |||||
PACKAGE_SUMMARY="ethermint enables ethereum as an ABCI application on tendermint and the COSMOS hub" | |||||
PACKAGE_URL="https://tendermint.com/" | |||||
PACKAGE_ADDITIONAL_HEADER="Provides: ethermint" | |||||
PACKAGE_DESCRIPTION="Ethermint enables ethereum to run as an ABCI application on tendermint and the COSMOS hub. This application allows you to get all the benefits of ethereum without having to run your own miners." | |||||
@ -0,0 +1,60 @@ | |||||
Version: @VERSION@ | |||||
Release: @BUILD_NUMBER@ | |||||
%define __spec_install_post %{nil} | |||||
%define debug_package %{nil} | |||||
%define __os_install_post %{nil} | |||||
Name: @PACKAGE_NAME@ | |||||
Summary: @PACKAGE_SUMMARY@ | |||||
License: Apache 2.0 | |||||
URL: @PACKAGE_URL@ | |||||
Packager: Greg Szabo | |||||
Requires: tendermint >= 0.11.0 | |||||
@PACKAGE_ADDITIONAL_HEADER@ | |||||
%description | |||||
@PACKAGE_DESCRIPTION@ | |||||
%pre | |||||
if ! %{__grep} -q '^%{name}:' /etc/passwd ; then | |||||
useradd -r -b %{_sysconfdir} %{name} | |||||
mkdir -p %{_sysconfdir}/%{name} | |||||
chmod 755 %{_sysconfdir}/%{name} | |||||
chown %{name}.%{name} %{_sysconfdir}/%{name} | |||||
fi | |||||
%prep | |||||
# Nothing to do here. - It is done in the Makefile. | |||||
%build | |||||
# Nothing to do here. | |||||
%install | |||||
cd %{name}-%{version}-%{release} | |||||
%{__cp} -a * %{buildroot} | |||||
%post | |||||
sudo -Hu %{name} tendermint init --home %{_sysconfdir}/%{name} | |||||
sudo -Hu %{name} %{name} --datadir %{_sysconfdir}/%{name} init %{_sysconfdir}/%{name}/genesis.json | |||||
systemctl daemon-reload | |||||
%preun | |||||
systemctl stop %{name} 2> /dev/null || : | |||||
systemctl stop %{name}-service 2> /dev/null || : | |||||
%postun | |||||
systemctl daemon-reload | |||||
%files | |||||
%attr(0755, %{name}, %{name}) %dir %{_sysconfdir}/%{name} | |||||
%config(noreplace) %attr(0644, %{name}, %{name}) %{_sysconfdir}/%{name}/genesis.json | |||||
%attr(0755, %{name}, %{name}) %dir %{_sysconfdir}/%{name}/keystore | |||||
%attr(0644, %{name}, %{name}) %{_sysconfdir}/%{name}/keystore/* | |||||
%{_bindir}/* | |||||
%{_sysconfdir}/systemd/system/* | |||||
%{_sysconfdir}/systemd/system-preset/* | |||||
%dir %{_defaultlicensedir}/%{name} | |||||
%doc %{_defaultlicensedir}/%{name}/LICENSE | |||||
@ -0,0 +1,5 @@ | |||||
PACKAGE_SUMMARY="gaia - Tendermint Cosmos delegation game chain" | |||||
PACKAGE_URL="https://cosmos.network/" | |||||
PACKAGE_ADDITIONAL_HEADER="" | |||||
PACKAGE_DESCRIPTION="Gaia description comes later." | |||||
@ -0,0 +1,31 @@ | |||||
Version: @VERSION@ | |||||
Release: @BUILD_NUMBER@ | |||||
%define __spec_install_post %{nil} | |||||
%define debug_package %{nil} | |||||
%define __os_install_post %{nil} | |||||
Name: tendermint | |||||
Summary: securely and consistently replicate an application on many machines | |||||
License: Apache 2.0 | |||||
URL: https://tendermint.com/ | |||||
Packager: Greg Szabo | |||||
%description | |||||
Tendermint is software for securely and consistently replicating an application on many machines. By securely, we mean that Tendermint works even if up to 1/3 of machines fail in arbitrary ways. By consistently, we mean that every non-faulty machine sees the same transaction log and computes the same state. | |||||
%prep | |||||
# Nothing to do here. - It is done in the Makefile. | |||||
%build | |||||
# Nothing to do here. | |||||
%install | |||||
cd %{name}-%{version}-%{release} | |||||
%{__cp} -a * %{buildroot} | |||||
%files | |||||
%{_bindir}/tendermint | |||||
%dir %{_defaultlicensedir}/%{name} | |||||
%doc %{_defaultlicensedir}/%{name}/LICENSE | |||||
@ -0,0 +1 @@ | |||||
deb http://tendermint-packages.s3-website-us-west-1.amazonaws.com/debian stable main |
@ -0,0 +1,12 @@ | |||||
#This is the .repo file for the Tendermint CentOS repositories. | |||||
#Although it has only been tested under CentOS 7, it should work under Fedora and RedHat 7 too. | |||||
#Currently only 64-bit packages are built. | |||||
[tendermint] | |||||
name=Tendermint stable releases repository | |||||
baseurl=https://do9rmxapsag1v.cloudfront.net/centos/7/os/x86_64 | |||||
gpgcheck=1 | |||||
gpgkey=https://do9rmxapsag1v.cloudfront.net/centos/7/os/x86_64/RPM-GPG-KEY-Tendermint | |||||
enabled=1 | |||||
#sslverify = 1 | |||||
@ -0,0 +1,6 @@ | |||||
tendermint (@VERSION@) @STABILITY@; urgency=medium | |||||
* Automatic build. See https://github.com/tendermint/tendermint for more information. | |||||
-- Greg Szabo <greg@philosobear.com> @DATETIMESTAMP@ | |||||
@ -0,0 +1 @@ | |||||
9 |
@ -0,0 +1,14 @@ | |||||
Source: tendermint | |||||
Section: net | |||||
Priority: optional | |||||
Maintainer: Greg Szabo <greg@philosobear.com> | |||||
Build-Depends: debhelper (>=9) | |||||
Standards-Version: 3.9.6 | |||||
Homepage: https://tendermint.com | |||||
Package: tendermint | |||||
Architecture: amd64 | |||||
Version: @VERSION@ | |||||
Installed-Size: @INSTALLEDSIZE@ | |||||
Description: securely and consistently replicate an application on many machines | |||||
Tendermint is software for securely and consistently replicating an application on many machines. By securely, we mean that Tendermint works even if up to 1/3 of machines fail in arbitrary ways. By consistently, we mean that every non-faulty machine sees the same transaction log and computes the same state. | |||||
@ -0,0 +1,21 @@ | |||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | |||||
Upstream-Name: tendermint | |||||
Source: https://github.com/tendermint/tendermint | |||||
Files: * | |||||
Copyright: 2017 All In Bits, Inc. | |||||
License: Apache-2.0 | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
. | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
. | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. | |||||
. | |||||
On Debian systems, the full text of the Apache License 2.0 can be found | |||||
in the file `/usr/share/common-licenses/Apache-2.0'. |
@ -0,0 +1,192 @@ | |||||
Copyright (C) 2017 Tendermint | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
https://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
https://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,290 @@ | |||||
Using Kubernetes | |||||
================ | |||||
.. figure:: assets/t_plus_k.png | |||||
:alt: Tendermint plus Kubernetes | |||||
Tendermint plus Kubernetes | |||||
This should primarily be used for testing purposes or for | |||||
tightly-defined chains operated by a single stakeholder (see `the | |||||
security precautions <#security>`__). If your desire is to launch an | |||||
application with many stakeholders, consider using our set of Ansible | |||||
scripts. | |||||
Quick Start | |||||
----------- | |||||
For either platform, see the `requirements <https://github.com/kubernetes/minikube#requirements>`__ | |||||
MacOS | |||||
^^^^^ | |||||
:: | |||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl | |||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ | |||||
minikube start | |||||
git clone https://github.com/tendermint/tools.git && cd tools/mintnet-kubernetes/examples/basecoin && make create | |||||
Linux | |||||
^^^^^ | |||||
:: | |||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl | |||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ | |||||
minikube start | |||||
git clone https://github.com/tendermint/tools.git && cd tools/mintnet-kubernetes/examples/basecoin && make create | |||||
Verify it worked | |||||
~~~~~~~~~~~~~~~~ | |||||
**Using a shell:** | |||||
First wait until all the pods are ``Running``: | |||||
``kubectl get pods -w -o wide -L tm`` | |||||
then query the Tendermint app logs from the first pod: | |||||
``kubectl logs -c tm -f tm-0`` | |||||
finally, use our `Rest API <../specification/rpc.html>`__ to fetch the status of the second pod's Tendermint app. | |||||
Note we are using ``kubectl exec`` because pods are not exposed (and should not be) to the | |||||
outer network: | |||||
``kubectl exec -c tm tm-0 -- curl -s http://tm-1.basecoin:26657/status | json_pp`` | |||||
**Using the dashboard:** | |||||
:: | |||||
minikube dashboard | |||||
Clean up | |||||
~~~~~~~~ | |||||
:: | |||||
make destroy | |||||
Usage | |||||
----- | |||||
Setup a Kubernetes cluster | |||||
^^^^^^^^^^^^^^^^^^^^^^^^^^ | |||||
- locally using `Minikube <https://github.com/kubernetes/minikube>`__ | |||||
- on GCE with a single click in the web UI | |||||
- on AWS using `Kubernetes | |||||
Operations <https://github.com/kubernetes/kops/blob/master/docs/aws.md>`__ | |||||
- on Linux machines (Digital Ocean) using | |||||
`kubeadm <https://kubernetes.io/docs/getting-started-guides/kubeadm/>`__ | |||||
- on AWS, Azure, GCE or bare metal using `Kargo | |||||
(Ansible) <https://kubernetes.io/docs/getting-started-guides/kargo/>`__ | |||||
Please refer to `the official | |||||
documentation <https://kubernetes.io/docs/getting-started-guides/>`__ | |||||
for overview and comparison of different options. | |||||
Kubernetes on Digital Ocean | |||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||||
Available options: | |||||
- `kubeadm (alpha) <https://kubernetes.io/docs/getting-started-guides/kubeadm/>`__ | |||||
- `kargo <https://kubernetes.io/docs/getting-started-guides/kargo/>`__ | |||||
- `rancher <http://rancher.com/>`__ | |||||
- `terraform <https://github.com/hermanjunge/kubernetes-digitalocean-terraform>`__ | |||||
As you can see, there is no single tool for creating a cluster on DO. | |||||
Therefore, choose the one you know and comfortable working with. If you know | |||||
and used `terraform <https://www.terraform.io/>`__ before, then choose it. If you | |||||
know Ansible, then pick kargo. If none of these seem familiar to you, go with | |||||
``kubeadm``. Rancher is a beautiful UI for deploying and managing containers in | |||||
production. | |||||
Kubernetes on Google Cloud Engine | |||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||||
Review the `Official Documentation <https://kubernetes.io/docs/getting-started-guides/gce/>`__ for Kubernetes on Google Compute | |||||
Engine. | |||||
**Create a cluster** | |||||
The recommended way is to use `Google Container | |||||
Engine <https://cloud.google.com/container-engine/>`__. You should be able | |||||
to create a fully fledged cluster with just a few clicks. | |||||
**Connect to it** | |||||
Install ``gcloud`` as a part of `Google Cloud SDK <https://cloud.google.com/sdk/>`__. | |||||
Make sure you have credentials for GCloud by running ``gcloud auth login``. | |||||
In order to make API calls against GCE, you must also run ``gcloud auth | |||||
application-default login``. | |||||
Press ``Connect``: | |||||
.. figure:: assets/gce1.png | |||||
and execute the first command in your shell. Then start a proxy by | |||||
executing ``kubectl` proxy``. | |||||
.. figure:: assets/gce2.png | |||||
Now you should be able to run ``kubectl`` command to create resources, get | |||||
resource info, logs, etc. | |||||
**Make sure you have Kubernetes >= 1.5, because you will be using | |||||
StatefulSets, which is a beta feature in 1.5.** | |||||
Create a configuration file | |||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |||||
Download a template: | |||||
:: | |||||
curl -Lo app.yaml https://github.com/tendermint/tools/raw/master/mintnet-kubernetes/app.template.yaml | |||||
Open ``app.yaml`` in your favorite editor and configure your app | |||||
container (navigate to ``- name: app``). Kubernetes DSL (Domain Specific | |||||
Language) is very simple, so it should be easy. You will need to set | |||||
Docker image, command and/or run arguments. Replace variables prefixed | |||||
with ``YOUR_APP`` with corresponding values. Set genesis time to now and | |||||
preferable chain ID in ConfigMap. | |||||
Please note if you are changing ``replicas`` number, do not forget to | |||||
update ``validators`` set in ConfigMap. You will be able to scale the | |||||
cluster up or down later, but new pods (nodes) won't become validators | |||||
automatically. | |||||
Deploy your application | |||||
^^^^^^^^^^^^^^^^^^^^^^^ | |||||
:: | |||||
kubectl create -f ./app.yaml | |||||
Observe your cluster | |||||
^^^^^^^^^^^^^^^^^^^^ | |||||
`web UI <https://github.com/kubernetes/dashboard>`__ | |||||
The easiest way to access Dashboard is to use ``kubectl``. Run the following | |||||
command in your desktop environment: | |||||
:: | |||||
kubectl proxy | |||||
``kubectl`` will handle authentication with apiserver and make Dashboard | |||||
available at http://localhost:8001/ui | |||||
**shell** | |||||
List all the pods: | |||||
:: | |||||
kubectl get pods -o wide -L tm | |||||
StatefulSet details: | |||||
:: | |||||
kubectl describe statefulsets tm | |||||
First pod details: | |||||
:: | |||||
kubectl describe pod tm-0 | |||||
Tendermint app logs from the first pod: | |||||
:: | |||||
kubectl logs tm-0 -c tm -f | |||||
App logs from the first pod: | |||||
:: | |||||
kubectl logs tm-0 -c app -f | |||||
Status of the second pod's Tendermint app: | |||||
:: | |||||
kubectl exec -c tm tm-0 -- curl -s http://tm-1.<YOUR_APP_NAME>:26657/status | json_pp | |||||
Security | |||||
-------- | |||||
Due to the nature of Kubernetes, where you typically have a single | |||||
master, the master could be a SPOF (Single Point Of Failure). Therefore, | |||||
you need to make sure only authorized people can access it. And these | |||||
people themselves had taken basic measures in order not to get hacked. | |||||
These are the best practices: | |||||
- all access to the master is over TLS | |||||
- access to the API Server is X.509 certificate or token based | |||||
- etcd is not exposed directly to the cluster | |||||
- ensure that images are free of vulnerabilities | |||||
(`1 <https://github.com/coreos/clair>`__) | |||||
- ensure that only authorized images are used in your environment | |||||
- disable direct access to Kubernetes nodes (no SSH) | |||||
- define resource quota | |||||
Resources: | |||||
- https://kubernetes.io/docs/admin/accessing-the-api/ | |||||
- http://blog.kubernetes.io/2016/08/security-best-practices-kubernetes-deployment.html | |||||
- https://blog.openshift.com/securing-kubernetes/ | |||||
Fault tolerance | |||||
--------------- | |||||
Having a single master (API server) is a bad thing also because if | |||||
something happens to it, you risk being left without an access to the | |||||
application. | |||||
To avoid that you can `run Kubernetes in multiple | |||||
zones <https://kubernetes.io/docs/admin/multiple-zones/>`__, each zone | |||||
running an `API | |||||
server <https://kubernetes.io/docs/admin/high-availability/>`__ and load | |||||
balance requests between them. Do not forget to make sure only one | |||||
instance of scheduler and controller-manager are running at once. | |||||
Running in multiple zones is a lightweight version of a broader `Cluster | |||||
Federation feature <https://kubernetes.io/docs/admin/federation/>`__. | |||||
Federated deployments could span across multiple regions (not zones). We | |||||
haven't tried this feature yet, so any feedback is highly appreciated! | |||||
Especially, related to additional latency and cost of exchanging data | |||||
between the regions. | |||||
Resources: | |||||
- https://kubernetes.io/docs/admin/high-availability/ | |||||
Starting process | |||||
---------------- | |||||
.. figure:: assets/statefulset.png | |||||
:alt: StatefulSet | |||||
StatefulSet | |||||
Init containers (``tm-gen-validator``) are run before all other | |||||
containers, creating public-private key pair for each pod. Every ``tm`` | |||||
container then asks other pods for their public keys, which are served | |||||
with nginx (``pub-key`` container). When ``tm`` container have all the | |||||
keys, it forms a genesis file and starts the Tendermint process. |
@ -0,0 +1,265 @@ | |||||
--- | |||||
apiVersion: v1 | |||||
kind: Service | |||||
metadata: | |||||
annotations: | |||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" | |||||
name: YOUR_APP_NAME | |||||
labels: | |||||
app: YOUR_APP_NAME | |||||
spec: | |||||
ports: | |||||
- port: 26656 | |||||
name: p2p | |||||
- port: 26657 | |||||
name: rpc | |||||
clusterIP: None | |||||
selector: | |||||
app: tm | |||||
--- | |||||
apiVersion: v1 | |||||
kind: ConfigMap | |||||
metadata: | |||||
name: tm-config | |||||
data: | |||||
seeds: "tm-0,tm-1,tm-2,tm-3" | |||||
validators: "tm-0,tm-1,tm-2,tm-3" | |||||
validator.power: "10" | |||||
genesis.json: |- | |||||
{ | |||||
"genesis_time": "2017-01-02T10:10:10.164Z", | |||||
"chain_id": "chain-B5XXm5", | |||||
"validators": [], | |||||
"app_hash": "" | |||||
} | |||||
pub_key_nginx.conf: |- | |||||
server { | |||||
listen 80 default_server; | |||||
listen [::]:80 default_server ipv6only=on; | |||||
location /pub_key.json { root /usr/share/nginx/; } | |||||
} | |||||
--- | |||||
apiVersion: policy/v1beta1 | |||||
kind: PodDisruptionBudget | |||||
metadata: | |||||
name: tm-budget | |||||
spec: | |||||
selector: | |||||
matchLabels: | |||||
app: tm | |||||
minAvailable: 2 | |||||
--- | |||||
apiVersion: apps/v1beta1 | |||||
kind: StatefulSet | |||||
metadata: | |||||
name: tm | |||||
spec: | |||||
serviceName: YOUR_APP_NAME | |||||
replicas: 4 | |||||
template: | |||||
metadata: | |||||
labels: | |||||
app: tm | |||||
version: v1 | |||||
annotations: | |||||
pod.beta.kubernetes.io/init-containers: '[{ | |||||
"name": "tm-gen-validator", | |||||
"image": "tendermint/tendermint:0.10.0", | |||||
"imagePullPolicy": "IfNotPresent", | |||||
"command": ["bash", "-c", " | |||||
set -ex\n | |||||
if [ ! -f /tendermint/priv_validator.json ]; then\n | |||||
tendermint gen_validator > /tendermint/priv_validator.json\n | |||||
# pub_key.json will be served by pub-key container\n | |||||
cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n | |||||
fi\n | |||||
"], | |||||
"volumeMounts": [ | |||||
{"name": "tmdir", "mountPath": "/tendermint"} | |||||
] | |||||
}]' | |||||
spec: | |||||
containers: | |||||
- name: tm | |||||
imagePullPolicy: IfNotPresent | |||||
image: tendermint/tendermint:0.10.0 | |||||
resources: | |||||
requests: | |||||
cpu: 50m | |||||
memory: 128Mi | |||||
limits: | |||||
cpu: 100m | |||||
memory: 256Mi | |||||
ports: | |||||
- containerPort: 26656 | |||||
name: p2p | |||||
- containerPort: 26657 | |||||
name: rpc | |||||
env: | |||||
- name: SEEDS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: seeds | |||||
- name: VALIDATOR_POWER | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validator.power | |||||
- name: VALIDATORS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validators | |||||
- name: TMHOME | |||||
value: /tendermint | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# copy template | |||||
cp /etc/tendermint/genesis.json /tendermint/genesis.json | |||||
# fill genesis file with validators | |||||
IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" | |||||
fqdn_suffix=$(hostname -f | sed 's#[^.]*\.\(\)#\1#') | |||||
for v in "${VALS_ARR[@]}"; do | |||||
# wait until validator generates priv/pub key pair | |||||
set +e | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
while [ "$ERR" != 0 ]; do | |||||
sleep 5 | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
done | |||||
set -e | |||||
# add validator to genesis file along with its pub_key | |||||
curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json | |||||
cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > tmpgenesis && mv tmpgenesis /tendermint/genesis.json | |||||
rm pub_validator.json | |||||
done | |||||
# construct seeds | |||||
IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" | |||||
seeds=() | |||||
for s in "${SEEDS_ARR[@]}"; do | |||||
seeds+=("$s.$fqdn_suffix:26656") | |||||
done | |||||
seeds=$(IFS=','; echo "${seeds[*]}") | |||||
tendermint node --p2p.seeds="$seeds" --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/tendermint/genesis.json | |||||
name: configdir | |||||
subPath: genesis.json | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
- name: app | |||||
imagePullPolicy: IfNotPresent | |||||
image: YOUR_APP_IMAGE | |||||
args: ["--addr=\"unix:///socks/app.sock\""] | |||||
volumeMounts: | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
######## OR ######## | |||||
# | |||||
# - name: app | |||||
# imagePullPolicy: IfNotPresent | |||||
# image: golang:1.7.5 | |||||
# resources: | |||||
# requests: | |||||
# cpu: YOUR_APP_CPU_REQ | |||||
# memory: YOUR_APP_MEM_REQ | |||||
# limits: | |||||
# cpu: YOUR_APP_CPU_LIMIT | |||||
# memory: YOUR_APP_MEM_LIMIT | |||||
# command: | |||||
# - bash | |||||
# - "-c" | |||||
# - | | |||||
# set -ex | |||||
# go get -d YOUR_APP_PACKAGE | |||||
# cd $GOPATH/YOUR_APP_PACKAGE | |||||
# make install | |||||
# | |||||
# rm -f /socks/app.sock # remove old socket | |||||
# YOUR_APP_EXEC --addr="unix:///socks/app.sock" | |||||
# volumeMounts: | |||||
# - name: socksdir | |||||
# mountPath: /socks | |||||
######## OPTIONALLY ######## | |||||
# | |||||
# - name: data | |||||
# imagePullPolicy: IfNotPresent | |||||
# image: golang:1.7.5 | |||||
# command: | |||||
# - bash | |||||
# - "-c" | |||||
# - | | |||||
# set -ex | |||||
# go get github.com/tendermint/merkleeyes/cmd/merkleeyes | |||||
# rm -f /socks/data.sock # remove old socket | |||||
# merkleeyes server --address="unix:///socks/data.sock" | |||||
# volumeMounts: | |||||
# - name: socksdir | |||||
# mountPath: /socks | |||||
- name: pub-key | |||||
imagePullPolicy: IfNotPresent | |||||
image: nginx:1.11.9 | |||||
resources: | |||||
requests: | |||||
cpu: 10m | |||||
memory: 12Mi | |||||
limits: | |||||
cpu: 20m | |||||
memory: 24Mi | |||||
ports: | |||||
- containerPort: 80 | |||||
name: pub-key | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) | |||||
# => we cannot serve from /tendermint, so we copy the file | |||||
mkdir -p /usr/share/nginx | |||||
cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json | |||||
nginx -g "daemon off;" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/nginx/conf.d/pub_key.conf | |||||
name: configdir | |||||
subPath: pub_key_nginx.conf | |||||
volumes: | |||||
- name: configdir | |||||
configMap: | |||||
name: tm-config | |||||
- name: socksdir | |||||
emptyDir: {} | |||||
volumeClaimTemplates: | |||||
- metadata: | |||||
name: tmdir | |||||
annotations: | |||||
volume.alpha.kubernetes.io/storage-class: anything | |||||
spec: | |||||
accessModes: ["ReadWriteOnce"] | |||||
resources: | |||||
requests: | |||||
storage: 2Gi |
@ -0,0 +1,10 @@ | |||||
create: | |||||
@echo "==> Creating deployment" | |||||
@kubectl create -f app.yaml | |||||
destroy: | |||||
@echo "==> Destroying deployment" | |||||
@kubectl delete -f app.yaml | |||||
@kubectl delete pvc -l app=tm | |||||
.PHONY: create destroy |
@ -0,0 +1,42 @@ | |||||
# Basecoin example | |||||
This is an example of using [basecoin](https://github.com/tendermint/basecoin). | |||||
## Usage | |||||
``` | |||||
make create | |||||
``` | |||||
### Check account balance and send a transaction | |||||
1. wait until all the pods are `Running`. | |||||
``` | |||||
kubectl get pods -w -o wide -L tm | |||||
``` | |||||
2. wait until app starts. | |||||
``` | |||||
kubectl logs -c app -f tm-0 | |||||
``` | |||||
3. get account's address of the second pod | |||||
``` | |||||
ADDR=`kubectl exec -c app tm-1 -- cat /app/key.json | jq ".address" | tr -d "\""` | |||||
``` | |||||
4. send 5 coins to it from the first pod | |||||
``` | |||||
kubectl exec -c app tm-0 -- basecoin tx send --to "0x$ADDR" --amount 5mycoin --from /app/key.json --chain_id chain-tTH4mi | |||||
``` | |||||
## Clean up | |||||
``` | |||||
make destroy | |||||
``` |
@ -0,0 +1,334 @@ | |||||
--- | |||||
apiVersion: v1 | |||||
kind: Service | |||||
metadata: | |||||
annotations: | |||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" | |||||
name: basecoin | |||||
labels: | |||||
app: basecoin | |||||
spec: | |||||
ports: | |||||
- port: 26656 | |||||
name: p2p | |||||
- port: 26657 | |||||
name: rpc | |||||
clusterIP: None | |||||
selector: | |||||
app: tm | |||||
--- | |||||
apiVersion: v1 | |||||
kind: ConfigMap | |||||
metadata: | |||||
name: tm-config | |||||
data: | |||||
seeds: "tm-0,tm-1,tm-2,tm-3" | |||||
validators: "tm-0,tm-1,tm-2,tm-3" | |||||
validator.power: "10" | |||||
genesis.json: |- | |||||
{ | |||||
"genesis_time": "2016-02-05T06:02:31.526Z", | |||||
"chain_id": "chain-tTH4mi", | |||||
"validators": [], | |||||
"app_hash": "" | |||||
} | |||||
pub_key_nginx.conf: |- | |||||
server { | |||||
listen 80 default_server; | |||||
listen [::]:80 default_server ipv6only=on; | |||||
location /pub_key.json { root /usr/share/nginx/; } | |||||
location /app_pub_key.json { root /usr/share/nginx/; } | |||||
} | |||||
--- | |||||
apiVersion: v1 | |||||
kind: ConfigMap | |||||
metadata: | |||||
name: app-config | |||||
data: | |||||
genesis.json: |- | |||||
{ | |||||
"chain_id": "chain-tTH4mi", | |||||
"app_options": { | |||||
"accounts": [ | |||||
{ | |||||
"pub_key": "tm-0", | |||||
"coins": [ | |||||
{ | |||||
"denom": "mycoin", | |||||
"amount": 1000000000 | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"pub_key": "tm-1", | |||||
"coins": [ | |||||
{ | |||||
"denom": "mycoin", | |||||
"amount": 1000000000 | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"pub_key": "tm-2", | |||||
"coins": [ | |||||
{ | |||||
"denom": "mycoin", | |||||
"amount": 1000000000 | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"pub_key": "tm-3", | |||||
"coins": [ | |||||
{ | |||||
"denom": "mycoin", | |||||
"amount": 1000000000 | |||||
} | |||||
] | |||||
} | |||||
] | |||||
} | |||||
} | |||||
--- | |||||
apiVersion: policy/v1beta1 | |||||
kind: PodDisruptionBudget | |||||
metadata: | |||||
name: tm-budget | |||||
spec: | |||||
selector: | |||||
matchLabels: | |||||
app: tm | |||||
minAvailable: 2 | |||||
--- | |||||
apiVersion: apps/v1beta1 | |||||
kind: StatefulSet | |||||
metadata: | |||||
name: tm | |||||
spec: | |||||
serviceName: basecoin | |||||
replicas: 4 | |||||
template: | |||||
metadata: | |||||
labels: | |||||
app: tm | |||||
annotations: | |||||
pod.beta.kubernetes.io/init-containers: '[{ | |||||
"name": "tm-gen-validator", | |||||
"image": "tendermint/tendermint:0.10.0", | |||||
"imagePullPolicy": "IfNotPresent", | |||||
"command": ["bash", "-c", " | |||||
set -ex\n | |||||
if [ ! -f /tendermint/priv_validator.json ]; then\n | |||||
tendermint gen_validator > /tendermint/priv_validator.json\n | |||||
# pub_key.json will be served by pub-key container\n | |||||
cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n | |||||
fi\n | |||||
"], | |||||
"volumeMounts": [ | |||||
{"name": "tmdir", "mountPath": "/tendermint"} | |||||
] | |||||
}, | |||||
{ | |||||
"name": "app-gen-key", | |||||
"image": "tendermint/basecoin:0.5.1", | |||||
"imagePullPolicy": "IfNotPresent", | |||||
"command": ["bash", "-c", " | |||||
set -ex\n | |||||
if [ ! -f /app/key.json ]; then\n | |||||
basecoin key new > /app/key.json\n | |||||
# pub_key.json will be served by app-pub-key container\n | |||||
cat /app/key.json | jq \".pub_key\" > /app/pub_key.json\n | |||||
fi\n | |||||
"], | |||||
"volumeMounts": [ | |||||
{"name": "appdir", "mountPath": "/app"} | |||||
] | |||||
}]' | |||||
spec: | |||||
containers: | |||||
- name: tm | |||||
imagePullPolicy: IfNotPresent | |||||
image: tendermint/tendermint:0.10.0 | |||||
ports: | |||||
- containerPort: 26656 | |||||
name: p2p | |||||
- containerPort: 26657 | |||||
name: rpc | |||||
env: | |||||
- name: SEEDS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: seeds | |||||
- name: VALIDATOR_POWER | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validator.power | |||||
- name: VALIDATORS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validators | |||||
- name: TMHOME | |||||
value: /tendermint | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# copy template | |||||
cp /etc/tendermint/genesis.json /tendermint/genesis.json | |||||
# fill genesis file with validators | |||||
IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" | |||||
fqdn_suffix=$(hostname -f | sed 's#[^.]*\.\(\)#\1#') | |||||
for v in "${VALS_ARR[@]}"; do | |||||
# wait until validator generates priv/pub key pair | |||||
set +e | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
while [ "$ERR" != 0 ]; do | |||||
sleep 5 | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
done | |||||
set -e | |||||
# add validator to genesis file along with its pub_key | |||||
curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json | |||||
cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > tmpgenesis && mv tmpgenesis /tendermint/genesis.json | |||||
rm pub_validator.json | |||||
done | |||||
# construct seeds | |||||
IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" | |||||
seeds=() | |||||
for s in "${SEEDS_ARR[@]}"; do | |||||
seeds+=("$s.$fqdn_suffix:26656") | |||||
done | |||||
seeds=$(IFS=','; echo "${seeds[*]}") | |||||
tendermint node --p2p.seeds="$seeds" --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/tendermint/genesis.json | |||||
name: tmconfigdir | |||||
subPath: genesis.json | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
- name: app | |||||
imagePullPolicy: IfNotPresent | |||||
image: tendermint/basecoin:0.5.1 | |||||
env: | |||||
- name: BCHOME | |||||
value: /app | |||||
workingDir: /app | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# replace "tm-N" with public keys in genesis file | |||||
cp /etc/app/genesis.json genesis.json | |||||
fqdn_suffix=$(hostname -f | sed 's#[^.]*\.\(\)#\1#') | |||||
# for every "base/account" | |||||
i=0 | |||||
length=$(cat genesis.json | jq ".app_options.accounts | length") | |||||
while [[ $i -lt $length ]]; do | |||||
# extract pod name ("tm-0") | |||||
pod=$(cat genesis.json | jq -r ".app_options.accounts[$i].pub_key") | |||||
# wait until pod starts to serve its pub_key | |||||
set +e | |||||
curl -s --fail "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null | |||||
ERR=$? | |||||
while [ "$ERR" != 0 ]; do | |||||
sleep 5 | |||||
curl -s --fail "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null | |||||
ERR=$? | |||||
done | |||||
set -e | |||||
# get its pub_key | |||||
curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" | jq "." > k.json | |||||
# replace pod name with it ("tm-0" => "{"type": ..., "data": ...}") | |||||
cat genesis.json | jq ".app_options.accounts[$i].pub_key = $(cat k.json | jq '.')" > tmpgenesis && mv tmpgenesis genesis.json | |||||
rm -f k.json | |||||
i=$((i+1)) | |||||
done | |||||
rm -f /socks/app.sock # remove old socket | |||||
basecoin start --address="unix:///socks/app.sock" --without-tendermint | |||||
volumeMounts: | |||||
- name: appdir | |||||
mountPath: /app | |||||
- mountPath: /etc/app/genesis.json | |||||
name: appconfigdir | |||||
subPath: genesis.json | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
- name: pub-key | |||||
imagePullPolicy: IfNotPresent | |||||
image: nginx:latest | |||||
ports: | |||||
- containerPort: 80 | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) | |||||
# => we cannot serve from /tendermint, so we copy the file | |||||
mkdir -p /usr/share/nginx | |||||
cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json | |||||
cp /app/pub_key.json /usr/share/nginx/app_pub_key.json | |||||
nginx -g "daemon off;" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- name: appdir | |||||
mountPath: /app | |||||
- mountPath: /etc/nginx/conf.d/pub_key.conf | |||||
name: tmconfigdir | |||||
subPath: pub_key_nginx.conf | |||||
volumes: | |||||
- name: tmconfigdir | |||||
configMap: | |||||
name: tm-config | |||||
- name: appconfigdir | |||||
configMap: | |||||
name: app-config | |||||
- name: socksdir | |||||
emptyDir: {} | |||||
volumeClaimTemplates: | |||||
- metadata: | |||||
name: tmdir | |||||
annotations: | |||||
volume.alpha.kubernetes.io/storage-class: anything | |||||
spec: | |||||
accessModes: [ "ReadWriteOnce" ] | |||||
resources: | |||||
requests: | |||||
storage: 2Gi | |||||
- metadata: | |||||
name: appdir | |||||
annotations: | |||||
volume.alpha.kubernetes.io/storage-class: anything | |||||
spec: | |||||
accessModes: [ "ReadWriteOnce" ] | |||||
resources: | |||||
requests: | |||||
storage: 12Mi |
@ -0,0 +1,100 @@ | |||||
**OUTDATED** | |||||
# Using with lightclient | |||||
We have an awesome cluster running, let's try to test this out without | |||||
relying on executing commands on the cluster. Rather, we can connect to the | |||||
rpc interface with the `light-client` package and execute commands locally, | |||||
or even proxy our webapp to the kubernetes backend. | |||||
## Setup | |||||
In order to get this working, we need to know a few pieces of info, | |||||
the chain id of tendermint, the chain id of basecoin, and an account | |||||
with a bit of cash.... | |||||
### Tendermint Chain ID | |||||
`kubectl exec -c tm tm-0 -- curl -s http://tm-1.basecoin:26657/status | json_pp | grep network` | |||||
set TM_CHAIN with the value there | |||||
### Basecoin Chain ID | |||||
`kubectl exec -c app tm-1 -- grep -A1 chainID /app/genesis.json` | |||||
set BC_CHAIN with the value there | |||||
### Expose tendermint rpc | |||||
We need to be able to reach the tendermint rpc interface from our shell. | |||||
`kubectl port-forward tm-0 26657:26657` | |||||
### Start basecoin-proxy | |||||
Using this info, let's connect our proxy and get going | |||||
`proxy-basecoin -tmchain=$TM_CHAIN -chain=$BC_CHAIN -rpc=localhost:26657` | |||||
## Basecoin accounts | |||||
Well, we can connect, but we don't have a registered account yet... | |||||
Let's look around, then use the cli to send some money from one of | |||||
the validators to our client's address so we can play. | |||||
**TODO** we can add some of our known accounts (from `/keys`) into | |||||
the genesis file, so we can skip all the kubectl money fiddling here. | |||||
We will want to start with money on some known non-validators. | |||||
### Getting validator info (kubectl) | |||||
The basecoin app deployment starts with 1000 "blank" coin in an account of | |||||
each validator. Let's get the address of the first validator | |||||
`kubectl exec -c app tm-1 -- grep address /app/key.json` | |||||
Store this info as VAL1_ADDR | |||||
### Querying state (proxy) | |||||
The proxy can read any public info via the tendermint rpc, so let's check | |||||
out this account. | |||||
`curl localhost:8108/query/account/$VAL1_ADDR` | |||||
Now, let's make out own account.... | |||||
`curl -XPOST http://localhost:8108/keys/ -d '{"name": "k8demo", "passphrase": "1234567890"}'` | |||||
(or pick your own user and password). Remember the address you get here. You can | |||||
always find it out later by calling: | |||||
`curl http://localhost:8108/keys/k8demo` | |||||
and store it in DEMO_ADDR, which is empty at first | |||||
`curl localhost:8108/query/account/$DEMO_ADDR` | |||||
### "Stealing" validator cash (kubectl) | |||||
Run one command, that will be signed, now we have money | |||||
`kubectl exec -c app tm-0 -- basecoin tx send --to <k8demo-address> --amount 500` | |||||
### Using our money | |||||
Returning to our remote shell, we have a remote account with some money. | |||||
Let's see that. | |||||
`curl localhost:8108/query/account/$DEMO_ADDR` | |||||
Cool. Now we need to send it to a second account. | |||||
`curl -XPOST http://localhost:8108/keys/ -d '{"name": "buddy", "passphrase": "1234567890"}'` | |||||
and store the resulting address in BUDDY_ADDR | |||||
**TODO** finish this | |||||
@ -0,0 +1,10 @@ | |||||
create: | |||||
@echo "==> Creating deployment" | |||||
@kubectl create -f app.yaml | |||||
destroy: | |||||
@echo "==> Destroying deployment" | |||||
@kubectl delete -f app.yaml | |||||
@kubectl delete pvc -l app=tm | |||||
.PHONY: create destroy |
@ -0,0 +1,214 @@ | |||||
--- | |||||
apiVersion: v1 | |||||
kind: Service | |||||
metadata: | |||||
annotations: | |||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" | |||||
name: counter | |||||
labels: | |||||
app: counter | |||||
spec: | |||||
ports: | |||||
- port: 26656 | |||||
name: p2p | |||||
- port: 26657 | |||||
name: rpc | |||||
clusterIP: None | |||||
selector: | |||||
app: tm | |||||
--- | |||||
apiVersion: v1 | |||||
kind: ConfigMap | |||||
metadata: | |||||
name: tm-config | |||||
data: | |||||
seeds: "tm-0,tm-1,tm-2,tm-3" | |||||
validators: "tm-0,tm-1,tm-2,tm-3" | |||||
validator.power: "10" | |||||
genesis.json: |- | |||||
{ | |||||
"genesis_time": "2016-02-05T23:17:31.164Z", | |||||
"chain_id": "chain-B5XXm5", | |||||
"validators": [], | |||||
"app_hash": "" | |||||
} | |||||
pub_key_nginx.conf: |- | |||||
server { | |||||
listen 80 default_server; | |||||
listen [::]:80 default_server ipv6only=on; | |||||
location /pub_key.json { root /usr/share/nginx/; } | |||||
} | |||||
--- | |||||
apiVersion: policy/v1beta1 | |||||
kind: PodDisruptionBudget | |||||
metadata: | |||||
name: tm-budget | |||||
spec: | |||||
selector: | |||||
matchLabels: | |||||
app: tm | |||||
minAvailable: 2 | |||||
--- | |||||
apiVersion: apps/v1beta1 | |||||
kind: StatefulSet | |||||
metadata: | |||||
name: tm | |||||
spec: | |||||
serviceName: counter | |||||
replicas: 4 | |||||
template: | |||||
metadata: | |||||
labels: | |||||
app: tm | |||||
annotations: | |||||
pod.beta.kubernetes.io/init-containers: '[{ | |||||
"name": "tm-gen-validator", | |||||
"image": "tendermint/tendermint:0.10.0", | |||||
"imagePullPolicy": "IfNotPresent", | |||||
"command": ["bash", "-c", " | |||||
set -ex\n | |||||
if [ ! -f /tendermint/priv_validator.json ]; then\n | |||||
tendermint gen_validator > /tendermint/priv_validator.json\n | |||||
# pub_key.json will be served by pub-key container\n | |||||
cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n | |||||
fi\n | |||||
"], | |||||
"volumeMounts": [ | |||||
{"name": "tmdir", "mountPath": "/tendermint"} | |||||
] | |||||
}]' | |||||
spec: | |||||
containers: | |||||
- name: tm | |||||
imagePullPolicy: IfNotPresent | |||||
image: tendermint/tendermint:0.10.0 | |||||
ports: | |||||
- containerPort: 26656 | |||||
name: p2p | |||||
- containerPort: 26657 | |||||
name: rpc | |||||
env: | |||||
- name: SEEDS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: seeds | |||||
- name: VALIDATOR_POWER | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validator.power | |||||
- name: VALIDATORS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validators | |||||
- name: TMHOME | |||||
value: /tendermint | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# copy template | |||||
cp /etc/tendermint/genesis.json /tendermint/genesis.json | |||||
# fill genesis file with validators | |||||
IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" | |||||
fqdn_suffix=$(hostname -f | sed 's#[^.]*\.\(\)#\1#') | |||||
for v in "${VALS_ARR[@]}"; do | |||||
# wait until validator generates priv/pub key pair | |||||
set +e | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
while [ "$ERR" != 0 ]; do | |||||
sleep 5 | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
done | |||||
set -e | |||||
# add validator to genesis file along with its pub_key | |||||
curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json | |||||
cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > tmpgenesis && mv tmpgenesis /tendermint/genesis.json | |||||
rm pub_validator.json | |||||
done | |||||
# construct seeds | |||||
IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" | |||||
seeds=() | |||||
for s in "${SEEDS_ARR[@]}"; do | |||||
seeds+=("$s.$fqdn_suffix:26656") | |||||
done | |||||
seeds=$(IFS=','; echo "${seeds[*]}") | |||||
tendermint node --p2p.seeds="$seeds" --moniker="`hostname`" --proxy_app="unix:///socks/app.sock" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/tendermint/genesis.json | |||||
name: tmconfigdir | |||||
subPath: genesis.json | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
- name: app | |||||
imagePullPolicy: IfNotPresent | |||||
image: golang:latest | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
go get github.com/tendermint/abci/cmd/counter | |||||
rm -f /socks/app.sock # remove old socket | |||||
counter --serial --addr="unix:///socks/app.sock" | |||||
volumeMounts: | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
- name: pub-key | |||||
imagePullPolicy: IfNotPresent | |||||
image: nginx:latest | |||||
ports: | |||||
- containerPort: 80 | |||||
name: pub-key | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) | |||||
# => we cannot serve from /tendermint, so we copy the file | |||||
mkdir -p /usr/share/nginx | |||||
cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json | |||||
nginx -g "daemon off;" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/nginx/conf.d/pub_key.conf | |||||
name: tmconfigdir | |||||
subPath: pub_key_nginx.conf | |||||
volumes: | |||||
- name: tmconfigdir | |||||
configMap: | |||||
name: tm-config | |||||
- name: socksdir | |||||
emptyDir: {} | |||||
volumeClaimTemplates: | |||||
- metadata: | |||||
name: tmdir | |||||
annotations: | |||||
volume.alpha.kubernetes.io/storage-class: anything | |||||
spec: | |||||
accessModes: ["ReadWriteOnce"] | |||||
resources: | |||||
requests: | |||||
storage: 2Gi |
@ -0,0 +1,17 @@ | |||||
create: | |||||
@echo "==> Creating deployment" | |||||
@kubectl create -f app.yaml | |||||
@echo "==> Waiting 10s until it is probably ready" | |||||
@sleep 10 | |||||
@echo "==> Creating monitor and transacter pods" | |||||
@kubectl create -f tm-monitor-pod.yaml | |||||
@kubectl create -f transacter-pod.yaml | |||||
destroy: | |||||
@echo "==> Destroying deployment" | |||||
@kubectl delete -f transacter-pod.yaml | |||||
@kubectl delete -f tm-monitor-pod.yaml | |||||
@kubectl delete -f app.yaml | |||||
@kubectl delete pvc -l app=tm | |||||
.PHONY: create destroy |
@ -0,0 +1,196 @@ | |||||
--- | |||||
apiVersion: v1 | |||||
kind: Service | |||||
metadata: | |||||
annotations: | |||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" | |||||
name: dummy | |||||
labels: | |||||
app: dummy | |||||
spec: | |||||
ports: | |||||
- port: 26656 | |||||
name: p2p | |||||
- port: 26657 | |||||
name: rpc | |||||
clusterIP: None | |||||
selector: | |||||
app: tm | |||||
--- | |||||
apiVersion: v1 | |||||
kind: ConfigMap | |||||
metadata: | |||||
name: tm-config | |||||
data: | |||||
seeds: "tm-0,tm-1,tm-2,tm-3" | |||||
validators: "tm-0,tm-1,tm-2,tm-3" | |||||
validator.power: "10" | |||||
genesis.json: |- | |||||
{ | |||||
"genesis_time": "2016-02-05T23:17:31.164Z", | |||||
"chain_id": "chain-B5XXm5", | |||||
"validators": [], | |||||
"app_hash": "" | |||||
} | |||||
pub_key_nginx.conf: |- | |||||
server { | |||||
listen 80 default_server; | |||||
listen [::]:80 default_server ipv6only=on; | |||||
location /pub_key.json { root /usr/share/nginx/; } | |||||
} | |||||
--- | |||||
apiVersion: policy/v1beta1 | |||||
kind: PodDisruptionBudget | |||||
metadata: | |||||
name: tm-budget | |||||
spec: | |||||
selector: | |||||
matchLabels: | |||||
app: tm | |||||
minAvailable: 2 | |||||
--- | |||||
apiVersion: apps/v1beta1 | |||||
kind: StatefulSet | |||||
metadata: | |||||
name: tm | |||||
spec: | |||||
serviceName: dummy | |||||
replicas: 4 | |||||
template: | |||||
metadata: | |||||
labels: | |||||
app: tm | |||||
annotations: | |||||
pod.beta.kubernetes.io/init-containers: '[{ | |||||
"name": "tm-gen-validator", | |||||
"image": "tendermint/tendermint:0.10.0", | |||||
"imagePullPolicy": "IfNotPresent", | |||||
"command": ["bash", "-c", " | |||||
set -ex\n | |||||
if [ ! -f /tendermint/priv_validator.json ]; then\n | |||||
tendermint gen_validator > /tendermint/priv_validator.json\n | |||||
# pub_key.json will be served by pub-key container\n | |||||
cat /tendermint/priv_validator.json | jq \".pub_key\" > /tendermint/pub_key.json\n | |||||
fi\n | |||||
"], | |||||
"volumeMounts": [ | |||||
{"name": "tmdir", "mountPath": "/tendermint"} | |||||
] | |||||
}]' | |||||
spec: | |||||
containers: | |||||
- name: tm | |||||
imagePullPolicy: IfNotPresent | |||||
image: tendermint/tendermint:0.10.0 | |||||
ports: | |||||
- containerPort: 26656 | |||||
name: p2p | |||||
- containerPort: 26657 | |||||
name: rpc | |||||
env: | |||||
- name: SEEDS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: seeds | |||||
- name: VALIDATOR_POWER | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validator.power | |||||
- name: VALIDATORS | |||||
valueFrom: | |||||
configMapKeyRef: | |||||
name: tm-config | |||||
key: validators | |||||
- name: TMHOME | |||||
value: /tendermint | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# copy template | |||||
cp /etc/tendermint/genesis.json /tendermint/genesis.json | |||||
# fill genesis file with validators | |||||
IFS=',' read -ra VALS_ARR <<< "$VALIDATORS" | |||||
fqdn_suffix=$(hostname -f | sed 's#[^.]*\.\(\)#\1#') | |||||
for v in "${VALS_ARR[@]}"; do | |||||
# wait until validator generates priv/pub key pair | |||||
set +e | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
while [ "$ERR" != 0 ]; do | |||||
sleep 5 | |||||
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null | |||||
ERR=$? | |||||
done | |||||
set -e | |||||
# add validator to genesis file along with its pub_key | |||||
curl -s "http://$v.$fqdn_suffix/pub_key.json" | jq ". as \$k | {pub_key: \$k, amount: $VALIDATOR_POWER, name: \"$v\"}" > pub_validator.json | |||||
cat /tendermint/genesis.json | jq ".validators |= .+ [$(cat pub_validator.json)]" > tmpgenesis && mv tmpgenesis /tendermint/genesis.json | |||||
rm pub_validator.json | |||||
done | |||||
# construct seeds | |||||
IFS=',' read -ra SEEDS_ARR <<< "$SEEDS" | |||||
seeds=() | |||||
for s in "${SEEDS_ARR[@]}"; do | |||||
seeds+=("$s.$fqdn_suffix:26656") | |||||
done | |||||
seeds=$(IFS=','; echo "${seeds[*]}") | |||||
tendermint node --p2p.seeds="$seeds" --moniker="`hostname`" --proxy_app="dummy" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/tendermint/genesis.json | |||||
name: tmconfigdir | |||||
subPath: genesis.json | |||||
- name: socksdir | |||||
mountPath: /socks | |||||
- name: pub-key | |||||
imagePullPolicy: IfNotPresent | |||||
image: nginx:latest | |||||
ports: | |||||
- containerPort: 80 | |||||
name: pub-key | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
# fixes 403 Permission Denied (open() "/tendermint/pub_key.json" failed (13: Permission denied)) | |||||
# => we cannot serve from /tendermint, so we copy the file | |||||
mkdir -p /usr/share/nginx | |||||
cp /tendermint/pub_key.json /usr/share/nginx/pub_key.json | |||||
nginx -g "daemon off;" | |||||
volumeMounts: | |||||
- name: tmdir | |||||
mountPath: /tendermint | |||||
- mountPath: /etc/nginx/conf.d/pub_key.conf | |||||
name: tmconfigdir | |||||
subPath: pub_key_nginx.conf | |||||
volumes: | |||||
- name: tmconfigdir | |||||
configMap: | |||||
name: tm-config | |||||
- name: socksdir | |||||
emptyDir: {} | |||||
volumeClaimTemplates: | |||||
- metadata: | |||||
name: tmdir | |||||
annotations: | |||||
volume.alpha.kubernetes.io/storage-class: anything | |||||
spec: | |||||
accessModes: ["ReadWriteOnce"] | |||||
resources: | |||||
requests: | |||||
storage: 2Gi |
@ -0,0 +1,13 @@ | |||||
--- | |||||
apiVersion: v1 | |||||
kind: Pod | |||||
metadata: | |||||
name: monitor | |||||
spec: | |||||
containers: | |||||
- name: monitor | |||||
image: tendermint/monitor | |||||
args: ["-listen-addr=tcp://0.0.0.0:26670", "tm-0.dummy:26657,tm-1.dummy:26657,tm-2.dummy:26657,tm-3.dummy:26657"] | |||||
ports: | |||||
- containerPort: 26670 | |||||
name: rpc |
@ -0,0 +1,19 @@ | |||||
--- | |||||
apiVersion: v1 | |||||
kind: Pod | |||||
metadata: | |||||
name: transacter | |||||
spec: | |||||
containers: | |||||
- name: transacter | |||||
image: tendermint/transacter | |||||
command: | |||||
- bash | |||||
- "-c" | |||||
- | | |||||
set -ex | |||||
while true | |||||
do | |||||
./transact 100 "tm-0.dummy:26657" | |||||
sleep 1 | |||||
done |
@ -0,0 +1,6 @@ | |||||
FROM alpine:3.7 | |||||
WORKDIR /app | |||||
COPY tm-bench /app/tm-bench | |||||
ENTRYPOINT ["./tm-bench"] |
@ -0,0 +1,12 @@ | |||||
FROM golang:latest | |||||
RUN mkdir -p /go/src/github.com/tendermint/tools/tm-bench | |||||
WORKDIR /go/src/github.com/tendermint/tools/tm-bench | |||||
COPY Makefile /go/src/github.com/tendermint/tools/tm-bench/ | |||||
RUN make get_tools | |||||
COPY . /go/src/github.com/tendermint/tools/tm-bench | |||||
RUN make get_vendor_deps |
@ -0,0 +1,337 @@ | |||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/beorn7/perks" | |||||
packages = ["quantile"] | |||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/btcsuite/btcd" | |||||
packages = ["btcec"] | |||||
revision = "675abc5df3c5531bc741b56a765e35623459da6d" | |||||
[[projects]] | |||||
name = "github.com/davecgh/go-spew" | |||||
packages = ["spew"] | |||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76" | |||||
version = "v1.1.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/ebuchman/fail-test" | |||||
packages = ["."] | |||||
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" | |||||
[[projects]] | |||||
name = "github.com/go-kit/kit" | |||||
packages = [ | |||||
"log", | |||||
"log/level", | |||||
"log/term", | |||||
"metrics", | |||||
"metrics/discard", | |||||
"metrics/internal/lv", | |||||
"metrics/prometheus" | |||||
] | |||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71" | |||||
version = "v0.6.0" | |||||
[[projects]] | |||||
name = "github.com/go-logfmt/logfmt" | |||||
packages = ["."] | |||||
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" | |||||
version = "v0.3.0" | |||||
[[projects]] | |||||
name = "github.com/go-stack/stack" | |||||
packages = ["."] | |||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" | |||||
version = "v1.7.0" | |||||
[[projects]] | |||||
name = "github.com/gogo/protobuf" | |||||
packages = [ | |||||
"gogoproto", | |||||
"jsonpb", | |||||
"proto", | |||||
"protoc-gen-gogo/descriptor", | |||||
"sortkeys", | |||||
"types" | |||||
] | |||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
name = "github.com/golang/protobuf" | |||||
packages = [ | |||||
"proto", | |||||
"ptypes", | |||||
"ptypes/any", | |||||
"ptypes/duration", | |||||
"ptypes/timestamp" | |||||
] | |||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/golang/snappy" | |||||
packages = ["."] | |||||
revision = "553a641470496b2327abcac10b36396bd98e45c9" | |||||
[[projects]] | |||||
name = "github.com/gorilla/websocket" | |||||
packages = ["."] | |||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" | |||||
version = "v1.2.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/jmhodges/levigo" | |||||
packages = ["."] | |||||
revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/kr/logfmt" | |||||
packages = ["."] | |||||
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" | |||||
[[projects]] | |||||
name = "github.com/matttproud/golang_protobuf_extensions" | |||||
packages = ["pbutil"] | |||||
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" | |||||
version = "v1.0.1" | |||||
[[projects]] | |||||
name = "github.com/pkg/errors" | |||||
packages = ["."] | |||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" | |||||
version = "v0.8.0" | |||||
[[projects]] | |||||
name = "github.com/prometheus/client_golang" | |||||
packages = [ | |||||
"prometheus", | |||||
"prometheus/promhttp" | |||||
] | |||||
revision = "c5b7fccd204277076155f10851dad72b76a49317" | |||||
version = "v0.8.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/prometheus/client_model" | |||||
packages = ["go"] | |||||
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/prometheus/common" | |||||
packages = [ | |||||
"expfmt", | |||||
"internal/bitbucket.org/ww/goautoneg", | |||||
"model" | |||||
] | |||||
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/prometheus/procfs" | |||||
packages = [ | |||||
".", | |||||
"internal/util", | |||||
"nfs", | |||||
"xfs" | |||||
] | |||||
revision = "7d6f385de8bea29190f15ba9931442a0eaef9af7" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/rcrowley/go-metrics" | |||||
packages = ["."] | |||||
revision = "e2704e165165ec55d062f5919b4b29494e9fa790" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/syndtr/goleveldb" | |||||
packages = [ | |||||
"leveldb", | |||||
"leveldb/cache", | |||||
"leveldb/comparer", | |||||
"leveldb/errors", | |||||
"leveldb/filter", | |||||
"leveldb/iterator", | |||||
"leveldb/journal", | |||||
"leveldb/memdb", | |||||
"leveldb/opt", | |||||
"leveldb/storage", | |||||
"leveldb/table", | |||||
"leveldb/util" | |||||
] | |||||
revision = "ae970a0732be3a1f5311da86118d37b9f4bd2a5a" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/tendermint/ed25519" | |||||
packages = [ | |||||
".", | |||||
"edwards25519", | |||||
"extra25519" | |||||
] | |||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" | |||||
[[projects]] | |||||
name = "github.com/tendermint/go-amino" | |||||
packages = ["."] | |||||
revision = "2106ca61d91029c931fd54968c2bb02dc96b1412" | |||||
version = "0.10.1" | |||||
[[projects]] | |||||
name = "github.com/tendermint/tendermint" | |||||
packages = [ | |||||
"abci/client", | |||||
"abci/example/code", | |||||
"abci/example/kvstore", | |||||
"abci/types", | |||||
"blockchain", | |||||
"config", | |||||
"consensus", | |||||
"consensus/types", | |||||
"crypto", | |||||
"crypto/tmhash", | |||||
"evidence", | |||||
"libs/events", | |||||
"libs/pubsub", | |||||
"libs/pubsub/query", | |||||
"mempool", | |||||
"node", | |||||
"p2p", | |||||
"p2p/conn", | |||||
"p2p/pex", | |||||
"p2p/upnp", | |||||
"privval", | |||||
"proxy", | |||||
"rpc/client", | |||||
"rpc/core", | |||||
"rpc/core/types", | |||||
"rpc/grpc", | |||||
"rpc/lib", | |||||
"rpc/lib/client", | |||||
"rpc/lib/server", | |||||
"rpc/lib/types", | |||||
"state", | |||||
"state/txindex", | |||||
"state/txindex/kv", | |||||
"state/txindex/null", | |||||
"types", | |||||
"version" | |||||
] | |||||
revision = "aa20c45ae99bb57a8efa2be7b09898e8967d1965" | |||||
version = "v0.21.1-rc1" | |||||
[[projects]] | |||||
branch = "develop" | |||||
name = "github.com/tendermint/tmlibs" | |||||
packages = [ | |||||
"autofile", | |||||
"clist", | |||||
"common", | |||||
"db", | |||||
"flowrate", | |||||
"log", | |||||
"merkle", | |||||
"merkle/tmhash" | |||||
] | |||||
revision = "56f44670ebd5a4e99e55f3f7d6f4d360ef5f1973" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "golang.org/x/crypto" | |||||
packages = [ | |||||
"curve25519", | |||||
"nacl/box", | |||||
"nacl/secretbox", | |||||
"openpgp/armor", | |||||
"openpgp/errors", | |||||
"poly1305", | |||||
"ripemd160", | |||||
"salsa20/salsa" | |||||
] | |||||
revision = "1f94bef427e370e425e55b172f3b5e300ef38304" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "golang.org/x/net" | |||||
packages = [ | |||||
"context", | |||||
"http/httpguts", | |||||
"http2", | |||||
"http2/hpack", | |||||
"idna", | |||||
"internal/timeseries", | |||||
"lex/httplex", | |||||
"netutil", | |||||
"trace" | |||||
] | |||||
revision = "640f4622ab692b87c2f3a94265e6f579fe38263d" | |||||
[[projects]] | |||||
name = "golang.org/x/text" | |||||
packages = [ | |||||
"collate", | |||||
"collate/build", | |||||
"internal/colltab", | |||||
"internal/gen", | |||||
"internal/tag", | |||||
"internal/triegen", | |||||
"internal/ucd", | |||||
"language", | |||||
"secure/bidirule", | |||||
"transform", | |||||
"unicode/bidi", | |||||
"unicode/cldr", | |||||
"unicode/norm", | |||||
"unicode/rangetable" | |||||
] | |||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" | |||||
version = "v0.3.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "google.golang.org/genproto" | |||||
packages = ["googleapis/rpc/status"] | |||||
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2" | |||||
[[projects]] | |||||
name = "google.golang.org/grpc" | |||||
packages = [ | |||||
".", | |||||
"balancer", | |||||
"codes", | |||||
"connectivity", | |||||
"credentials", | |||||
"grpclb/grpc_lb_v1/messages", | |||||
"grpclog", | |||||
"internal", | |||||
"keepalive", | |||||
"metadata", | |||||
"naming", | |||||
"peer", | |||||
"resolver", | |||||
"stats", | |||||
"status", | |||||
"tap", | |||||
"transport" | |||||
] | |||||
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" | |||||
version = "v1.7.5" | |||||
[solve-meta] | |||||
analyzer-name = "dep" | |||||
analyzer-version = 1 | |||||
inputs-digest = "65c380641dcebca21a6343c9ea36bab5cbebff696e27c74d0735077f63272248" | |||||
solver-name = "gps-cdcl" | |||||
solver-version = 1 |
@ -0,0 +1,54 @@ | |||||
# Gopkg.toml example | |||||
# | |||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html | |||||
# for detailed Gopkg.toml documentation. | |||||
# | |||||
# required = ["github.com/user/thing/cmd/thing"] | |||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] | |||||
# | |||||
# [[constraint]] | |||||
# name = "github.com/user/project" | |||||
# version = "1.0.0" | |||||
# | |||||
# [[constraint]] | |||||
# name = "github.com/user/project2" | |||||
# branch = "dev" | |||||
# source = "github.com/myfork/project2" | |||||
# | |||||
# [[override]] | |||||
# name = "github.com/x/y" | |||||
# version = "2.4.0" | |||||
# | |||||
# [prune] | |||||
# non-go = false | |||||
# go-tests = true | |||||
# unused-packages = true | |||||
[[constraint]] | |||||
name = "github.com/go-kit/kit" | |||||
version = "^0.6.0" | |||||
[[constraint]] | |||||
name = "github.com/gorilla/websocket" | |||||
version = "^1.2.0" | |||||
[[constraint]] | |||||
name = "github.com/pkg/errors" | |||||
version = "^0.8.0" | |||||
[[constraint]] | |||||
branch = "master" | |||||
name = "github.com/rcrowley/go-metrics" | |||||
[[constraint]] | |||||
name = "github.com/tendermint/tendermint" | |||||
branch = "develop" | |||||
[[constraint]] | |||||
name = "github.com/tendermint/tmlibs" | |||||
branch = "develop" | |||||
[prune] | |||||
go-tests = true | |||||
unused-packages = true |
@ -0,0 +1,204 @@ | |||||
Tendermint Bench | |||||
Copyright 2017 Tendermint | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
http://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
APPENDIX: How to apply the Apache License to your work. | |||||
To apply the Apache License to your work, attach the following | |||||
boilerplate notice, with the fields enclosed by brackets "[]" | |||||
replaced with your own identifying information. (Don't include | |||||
the brackets!) The text should be enclosed in the appropriate | |||||
comment syntax for the file format. We also recommend that a | |||||
file or class name and description of purpose be included on the | |||||
same "printed page" as the copyright notice for easier | |||||
identification within third-party archives. | |||||
Copyright [yyyy] [name of copyright owner] | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,116 @@ | |||||
DIST_DIRS := find * -type d -exec | |||||
VERSION := $(shell perl -ne '/^var version.*"([^"]+)".*$$/ && print "v$$1\n"' main.go) | |||||
GOTOOLS = \ | |||||
github.com/mitchellh/gox \ | |||||
github.com/golang/dep/cmd/dep \ | |||||
gopkg.in/alecthomas/gometalinter.v2 | |||||
all: check get_vendor_deps build test install metalinter | |||||
check: check_tools | |||||
######################################## | |||||
### Tools & dependencies | |||||
check_tools: | |||||
@# https://stackoverflow.com/a/25668869 | |||||
@echo "Found tools: $(foreach tool,$(GOTOOLS_CHECK),\ | |||||
$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))" | |||||
get_tools: | |||||
@echo "--> Installing tools" | |||||
go get -u -v $(GOTOOLS) | |||||
@gometalinter.v2 --install | |||||
update_tools: | |||||
@echo "--> Updating tools" | |||||
@go get -u $(GOTOOLS) | |||||
get_vendor_deps: | |||||
@rm -rf vendor/ | |||||
@echo "--> Running dep ensure" | |||||
@dep ensure | |||||
######################################## | |||||
### Build | |||||
build: | |||||
@go build | |||||
install: | |||||
@go install | |||||
test: | |||||
@go test -race | |||||
build-all: check_tools | |||||
rm -rf ./dist | |||||
gox -verbose \ | |||||
-ldflags "-s -w" \ | |||||
-arch="amd64 386 arm arm64" \ | |||||
-os="linux darwin windows freebsd" \ | |||||
-osarch="!darwin/arm !darwin/arm64" \ | |||||
-output="dist/{{.OS}}-{{.Arch}}/{{.Dir}}" . | |||||
dist: build-all | |||||
cd dist && \ | |||||
$(DIST_DIRS) cp ../LICENSE {} \; && \ | |||||
$(DIST_DIRS) cp ../README.rst {} \; && \ | |||||
$(DIST_DIRS) tar -zcf tm-bench-${VERSION}-{}.tar.gz {} \; && \ | |||||
shasum -a256 ./*.tar.gz > "./tm-bench_${VERSION}_SHA256SUMS" && \ | |||||
cd .. | |||||
######################################## | |||||
### Docker | |||||
build-docker: | |||||
rm -f ./tm-bench | |||||
docker run -it --rm -v "$(PWD):/go/src/app" -w "/go/src/app" -e "CGO_ENABLED=0" golang:alpine go build -ldflags "-s -w" -o tm-bench | |||||
docker build -t "tendermint/bench" . | |||||
clean: | |||||
rm -f ./tm-bench | |||||
rm -rf ./dist | |||||
######################################## | |||||
### Formatting, linting, and vetting | |||||
fmt: | |||||
@go fmt ./... | |||||
metalinter: | |||||
@echo "==> Running linter" | |||||
gometalinter.v2 --vendor --deadline=600s --disable-all \ | |||||
--enable=maligned \ | |||||
--enable=deadcode \ | |||||
--enable=goconst \ | |||||
--enable=goimports \ | |||||
--enable=gosimple \ | |||||
--enable=ineffassign \ | |||||
--enable=megacheck \ | |||||
--enable=misspell \ | |||||
--enable=staticcheck \ | |||||
--enable=safesql \ | |||||
--enable=structcheck \ | |||||
--enable=unconvert \ | |||||
--enable=unused \ | |||||
--enable=varcheck \ | |||||
--enable=vetshadow \ | |||||
./... | |||||
#--enable=gas \ | |||||
#--enable=dupl \ | |||||
#--enable=errcheck \ | |||||
#--enable=gocyclo \ | |||||
#--enable=golint \ <== comments on anything exported | |||||
#--enable=gotype \ | |||||
#--enable=interfacer \ | |||||
#--enable=unparam \ | |||||
#--enable=vet \ | |||||
metalinter_all: | |||||
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./... | |||||
# To avoid unintended conflicts with file names, always add to .PHONY | |||||
# unless there is a reason not to. | |||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html | |||||
.PHONY: check check_tools get_tools update_tools get_vendor_deps build install test build-all dist fmt metalinter metalinter_all build-docker clean |
@ -0,0 +1,69 @@ | |||||
# tm-bench | |||||
Tendermint blockchain benchmarking tool: | |||||
- https://github.com/tendermint/tools/tree/master/tm-bench | |||||
For example, the following: | |||||
tm-bench -T 10 -r 1000 localhost:26657 | |||||
will output: | |||||
Stats Avg StdDev Max Total | |||||
Txs/sec 818 532 1549 9000 | |||||
Blocks/sec 0.818 0.386 1 9 | |||||
## Quick Start | |||||
[Install Tendermint](https://github.com/tendermint/tendermint#install) | |||||
This currently is setup to work on tendermint's develop branch. Please ensure | |||||
you are on that. (If not, update `tendermint` and `tmlibs` in gopkg.toml to use | |||||
the master branch.) | |||||
then run: | |||||
tendermint init | |||||
tendermint node --proxy_app=kvstore | |||||
tm-bench localhost:26657 | |||||
with the last command being in a seperate window. | |||||
## Usage | |||||
tm-bench [-c 1] [-T 10] [-r 1000] [-s 250] [endpoints] | |||||
Examples: | |||||
tm-bench localhost:26657 | |||||
Flags: | |||||
-T int | |||||
Exit after the specified amount of time in seconds (default 10) | |||||
-c int | |||||
Connections to keep open per endpoint (default 1) | |||||
-r int | |||||
Txs per second to send in a connection (default 1000) | |||||
-s int | |||||
Size per tx in bytes | |||||
-v Verbose output | |||||
## How stats are collected | |||||
These stats are derived by having each connection send transactions at the | |||||
specified rate (or as close as it can get) for the specified time. After the | |||||
specified time, it iterates over all of the blocks that were created in that | |||||
time. The average and stddev per second are computed based off of that, by | |||||
grouping the data by second. | |||||
To send transactions at the specified rate in each connection, we loop | |||||
through the number of transactions. If its too slow, the loop stops at one second. | |||||
If its too fast, we wait until the one second mark ends. The transactions per | |||||
second stat is computed based off of what ends up in the block. | |||||
Each of the connections is handled via two separate goroutines. | |||||
## Development | |||||
make get_vendor_deps | |||||
make test |
@ -0,0 +1,18 @@ | |||||
package main | |||||
import ( | |||||
"testing" | |||||
"time" | |||||
) | |||||
func BenchmarkTimingPerTx(b *testing.B) { | |||||
startTime := time.Now() | |||||
endTime := startTime.Add(time.Second) | |||||
for i := 0; i < b.N; i++ { | |||||
if i%20 == 0 { | |||||
if time.Now().After(endTime) { | |||||
continue | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,300 @@ | |||||
package main | |||||
import ( | |||||
"encoding/json" | |||||
"flag" | |||||
"fmt" | |||||
"math" | |||||
"os" | |||||
"strings" | |||||
"text/tabwriter" | |||||
"time" | |||||
"github.com/go-kit/kit/log/term" | |||||
metrics "github.com/rcrowley/go-metrics" | |||||
tmrpc "github.com/tendermint/tendermint/rpc/client" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
var version = "0.3.0" | |||||
var logger = log.NewNopLogger() | |||||
type statistics struct { | |||||
TxsThroughput metrics.Histogram `json:"txs_per_sec"` | |||||
BlocksThroughput metrics.Histogram `json:"blocks_per_sec"` | |||||
} | |||||
func main() { | |||||
var duration, txsRate, connections, txSize int | |||||
var verbose bool | |||||
var outputFormat, broadcastTxMethod string | |||||
flag.IntVar(&connections, "c", 1, "Connections to keep open per endpoint") | |||||
flag.IntVar(&duration, "T", 10, "Exit after the specified amount of time in seconds") | |||||
flag.IntVar(&txsRate, "r", 1000, "Txs per second to send in a connection") | |||||
flag.IntVar(&txSize, "s", 250, "The size of a transaction in bytes.") | |||||
flag.StringVar(&outputFormat, "output-format", "plain", "Output format: plain or json") | |||||
flag.StringVar(&broadcastTxMethod, "broadcast-tx-method", "async", "Broadcast method: async (no guarantees; fastest), sync (ensures tx is checked) or commit (ensures tx is checked and committed; slowest)") | |||||
flag.BoolVar(&verbose, "v", false, "Verbose output") | |||||
flag.Usage = func() { | |||||
fmt.Println(`Tendermint blockchain benchmarking tool. | |||||
Usage: | |||||
tm-bench [-c 1] [-T 10] [-r 1000] [endpoints] [-output-format <plain|json> [-broadcast-tx-method <async|sync|commit>]] | |||||
Examples: | |||||
tm-bench localhost:26657`) | |||||
fmt.Println("Flags:") | |||||
flag.PrintDefaults() | |||||
} | |||||
flag.Parse() | |||||
if flag.NArg() == 0 { | |||||
flag.Usage() | |||||
os.Exit(1) | |||||
} | |||||
if verbose { | |||||
if outputFormat == "json" { | |||||
fmt.Fprintln(os.Stderr, "Verbose mode not supported with json output.") | |||||
os.Exit(1) | |||||
} | |||||
// Color errors red | |||||
colorFn := func(keyvals ...interface{}) term.FgBgColor { | |||||
for i := 1; i < len(keyvals); i += 2 { | |||||
if _, ok := keyvals[i].(error); ok { | |||||
return term.FgBgColor{Fg: term.White, Bg: term.Red} | |||||
} | |||||
} | |||||
return term.FgBgColor{} | |||||
} | |||||
logger = log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn) | |||||
fmt.Printf("Running %ds test @ %s\n", duration, flag.Arg(0)) | |||||
} | |||||
if broadcastTxMethod != "async" && | |||||
broadcastTxMethod != "sync" && | |||||
broadcastTxMethod != "commit" { | |||||
fmt.Fprintln( | |||||
os.Stderr, | |||||
"broadcast-tx-method should be either 'sync', 'async' or 'commit'.", | |||||
) | |||||
os.Exit(1) | |||||
} | |||||
var ( | |||||
endpoints = strings.Split(flag.Arg(0), ",") | |||||
client = tmrpc.NewHTTP(endpoints[0], "/websocket") | |||||
initialHeight = latestBlockHeight(client) | |||||
) | |||||
logger.Info("Latest block height", "h", initialHeight) | |||||
// record time start | |||||
timeStart := time.Now() | |||||
logger.Info("Time started", "t", timeStart) | |||||
transacters := startTransacters( | |||||
endpoints, | |||||
connections, | |||||
txsRate, | |||||
txSize, | |||||
"broadcast_tx_"+broadcastTxMethod, | |||||
) | |||||
endTime := time.Duration(duration) * time.Second | |||||
select { | |||||
case <-time.After(endTime): | |||||
for i, t := range transacters { | |||||
t.Stop() | |||||
numCrashes := countCrashes(t.connsBroken) | |||||
if numCrashes != 0 { | |||||
fmt.Printf("%d connections crashed on transacter #%d\n", numCrashes, i) | |||||
} | |||||
} | |||||
timeStop := time.Now() | |||||
logger.Info("Time stopped", "t", timeStop) | |||||
stats, err := calculateStatistics( | |||||
client, | |||||
initialHeight, | |||||
timeStart, | |||||
timeStop, | |||||
duration, | |||||
) | |||||
if err != nil { | |||||
fmt.Fprintln(os.Stderr, err) | |||||
os.Exit(1) | |||||
} | |||||
printStatistics(stats, outputFormat) | |||||
return | |||||
} | |||||
} | |||||
func latestBlockHeight(client tmrpc.Client) int64 { | |||||
status, err := client.Status() | |||||
if err != nil { | |||||
fmt.Fprintln(os.Stderr, err) | |||||
os.Exit(1) | |||||
} | |||||
return status.SyncInfo.LatestBlockHeight | |||||
} | |||||
func countCrashes(crashes []bool) int { | |||||
count := 0 | |||||
for i := 0; i < len(crashes); i++ { | |||||
if crashes[i] { | |||||
count++ | |||||
} | |||||
} | |||||
return count | |||||
} | |||||
// calculateStatistics calculates the tx / second, and blocks / second based | |||||
// off of the number the transactions and number of blocks that occurred from | |||||
// the start block, and the end time. | |||||
func calculateStatistics( | |||||
client tmrpc.Client, | |||||
minHeight int64, | |||||
timeStart, timeStop time.Time, | |||||
duration int, | |||||
) (*statistics, error) { | |||||
stats := &statistics{ | |||||
BlocksThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)), | |||||
TxsThroughput: metrics.NewHistogram(metrics.NewUniformSample(1000)), | |||||
} | |||||
// get blocks between minHeight and last height | |||||
// This returns max(minHeight,(last_height - 20)) to last_height | |||||
info, err := client.BlockchainInfo(minHeight, 0) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
var ( | |||||
blockMetas = info.BlockMetas | |||||
lastHeight = info.LastHeight | |||||
diff = lastHeight - minHeight | |||||
offset = len(blockMetas) | |||||
) | |||||
for offset < int(diff) { | |||||
// get blocks between minHeight and last height | |||||
info, err := client.BlockchainInfo(minHeight, lastHeight-int64(offset)) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
blockMetas = append(blockMetas, info.BlockMetas...) | |||||
offset = len(blockMetas) | |||||
} | |||||
var ( | |||||
numBlocksPerSec = make(map[int64]int64) | |||||
numTxsPerSec = make(map[int64]int64) | |||||
) | |||||
// because during some seconds blocks won't be created... | |||||
for i := int64(0); i < int64(duration); i++ { | |||||
numBlocksPerSec[i] = 0 | |||||
numTxsPerSec[i] = 0 | |||||
} | |||||
// iterates from max height to min height | |||||
for _, blockMeta := range blockMetas { | |||||
// check if block was created after timeStart | |||||
if blockMeta.Header.Time.Before(timeStart) { | |||||
break | |||||
} | |||||
// check if block was created before timeStop | |||||
if blockMeta.Header.Time.After(timeStop) { | |||||
continue | |||||
} | |||||
sec := secondsSinceTimeStart(timeStart, blockMeta.Header.Time) | |||||
// increase number of blocks for that second | |||||
numBlocksPerSec[sec]++ | |||||
// increase number of txs for that second | |||||
numTxsPerSec[sec] += blockMeta.Header.NumTxs | |||||
} | |||||
for _, n := range numBlocksPerSec { | |||||
stats.BlocksThroughput.Update(n) | |||||
} | |||||
for _, n := range numTxsPerSec { | |||||
stats.TxsThroughput.Update(n) | |||||
} | |||||
return stats, nil | |||||
} | |||||
func secondsSinceTimeStart(timeStart, timePassed time.Time) int64 { | |||||
return int64(math.Round(timePassed.Sub(timeStart).Seconds())) | |||||
} | |||||
func startTransacters( | |||||
endpoints []string, | |||||
connections, | |||||
txsRate int, | |||||
txSize int, | |||||
broadcastTxMethod string, | |||||
) []*transacter { | |||||
transacters := make([]*transacter, len(endpoints)) | |||||
for i, e := range endpoints { | |||||
t := newTransacter(e, connections, txsRate, txSize, broadcastTxMethod) | |||||
t.SetLogger(logger) | |||||
if err := t.Start(); err != nil { | |||||
fmt.Fprintln(os.Stderr, err) | |||||
os.Exit(1) | |||||
} | |||||
transacters[i] = t | |||||
} | |||||
return transacters | |||||
} | |||||
func printStatistics(stats *statistics, outputFormat string) { | |||||
if outputFormat == "json" { | |||||
result, err := json.Marshal(struct { | |||||
TxsThroughput float64 `json:"txs_per_sec_avg"` | |||||
BlocksThroughput float64 `json:"blocks_per_sec_avg"` | |||||
}{stats.TxsThroughput.Mean(), stats.BlocksThroughput.Mean()}) | |||||
if err != nil { | |||||
fmt.Fprintln(os.Stderr, err) | |||||
os.Exit(1) | |||||
} | |||||
fmt.Println(string(result)) | |||||
} else { | |||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0) | |||||
fmt.Fprintln(w, "Stats\tAvg\tStdDev\tMax\tTotal\t") | |||||
fmt.Fprintln( | |||||
w, | |||||
fmt.Sprintf( | |||||
"Txs/sec\t%.0f\t%.0f\t%d\t%d\t", | |||||
stats.TxsThroughput.Mean(), | |||||
stats.TxsThroughput.StdDev(), | |||||
stats.TxsThroughput.Max(), | |||||
stats.TxsThroughput.Sum(), | |||||
), | |||||
) | |||||
fmt.Fprintln( | |||||
w, | |||||
fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t%d\t", | |||||
stats.BlocksThroughput.Mean(), | |||||
stats.BlocksThroughput.StdDev(), | |||||
stats.BlocksThroughput.Max(), | |||||
stats.BlocksThroughput.Sum(), | |||||
), | |||||
) | |||||
w.Flush() | |||||
} | |||||
} |
@ -0,0 +1,252 @@ | |||||
package main | |||||
import ( | |||||
"crypto/md5" | |||||
"encoding/binary" | |||||
"encoding/hex" | |||||
"encoding/json" | |||||
"fmt" | |||||
"math/rand" | |||||
"net" | |||||
"net/http" | |||||
"net/url" | |||||
"os" | |||||
"sync" | |||||
"time" | |||||
"github.com/gorilla/websocket" | |||||
"github.com/pkg/errors" | |||||
rpctypes "github.com/tendermint/tendermint/rpc/lib/types" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
const ( | |||||
sendTimeout = 10 * time.Second | |||||
// see https://github.com/tendermint/go-rpc/blob/develop/server/handlers.go#L313 | |||||
pingPeriod = (30 * 9 / 10) * time.Second | |||||
) | |||||
type transacter struct { | |||||
Target string | |||||
Rate int | |||||
Size int | |||||
Connections int | |||||
BroadcastTxMethod string | |||||
conns []*websocket.Conn | |||||
connsBroken []bool | |||||
wg sync.WaitGroup | |||||
stopped bool | |||||
logger log.Logger | |||||
} | |||||
func newTransacter(target string, connections, rate int, size int, broadcastTxMethod string) *transacter { | |||||
return &transacter{ | |||||
Target: target, | |||||
Rate: rate, | |||||
Size: size, | |||||
Connections: connections, | |||||
BroadcastTxMethod: broadcastTxMethod, | |||||
conns: make([]*websocket.Conn, connections), | |||||
connsBroken: make([]bool, connections), | |||||
logger: log.NewNopLogger(), | |||||
} | |||||
} | |||||
// SetLogger lets you set your own logger | |||||
func (t *transacter) SetLogger(l log.Logger) { | |||||
t.logger = l | |||||
} | |||||
// Start opens N = `t.Connections` connections to the target and creates read | |||||
// and write goroutines for each connection. | |||||
func (t *transacter) Start() error { | |||||
t.stopped = false | |||||
rand.Seed(time.Now().Unix()) | |||||
for i := 0; i < t.Connections; i++ { | |||||
c, _, err := connect(t.Target) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
t.conns[i] = c | |||||
} | |||||
t.wg.Add(2 * t.Connections) | |||||
for i := 0; i < t.Connections; i++ { | |||||
go t.sendLoop(i) | |||||
go t.receiveLoop(i) | |||||
} | |||||
return nil | |||||
} | |||||
// Stop closes the connections. | |||||
func (t *transacter) Stop() { | |||||
t.stopped = true | |||||
t.wg.Wait() | |||||
for _, c := range t.conns { | |||||
c.Close() | |||||
} | |||||
} | |||||
// receiveLoop reads messages from the connection (empty in case of | |||||
// `broadcast_tx_async`). | |||||
func (t *transacter) receiveLoop(connIndex int) { | |||||
c := t.conns[connIndex] | |||||
defer t.wg.Done() | |||||
for { | |||||
_, _, err := c.ReadMessage() | |||||
if err != nil { | |||||
if !websocket.IsCloseError(err, websocket.CloseNormalClosure) { | |||||
t.logger.Error( | |||||
fmt.Sprintf("failed to read response on conn %d", connIndex), | |||||
"err", | |||||
err, | |||||
) | |||||
} | |||||
return | |||||
} | |||||
if t.stopped || t.connsBroken[connIndex] { | |||||
return | |||||
} | |||||
} | |||||
} | |||||
// sendLoop generates transactions at a given rate. | |||||
func (t *transacter) sendLoop(connIndex int) { | |||||
c := t.conns[connIndex] | |||||
c.SetPingHandler(func(message string) error { | |||||
err := c.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(sendTimeout)) | |||||
if err == websocket.ErrCloseSent { | |||||
return nil | |||||
} else if e, ok := err.(net.Error); ok && e.Temporary() { | |||||
return nil | |||||
} | |||||
return err | |||||
}) | |||||
logger := t.logger.With("addr", c.RemoteAddr()) | |||||
var txNumber = 0 | |||||
pingsTicker := time.NewTicker(pingPeriod) | |||||
txsTicker := time.NewTicker(1 * time.Second) | |||||
defer func() { | |||||
pingsTicker.Stop() | |||||
txsTicker.Stop() | |||||
t.wg.Done() | |||||
}() | |||||
// hash of the host name is a part of each tx | |||||
var hostnameHash [md5.Size]byte | |||||
hostname, err := os.Hostname() | |||||
if err != nil { | |||||
hostname = "127.0.0.1" | |||||
} | |||||
hostnameHash = md5.Sum([]byte(hostname)) | |||||
for { | |||||
select { | |||||
case <-txsTicker.C: | |||||
startTime := time.Now() | |||||
endTime := startTime.Add(time.Second) | |||||
numTxSent := t.Rate | |||||
for i := 0; i < t.Rate; i++ { | |||||
// each transaction embeds connection index, tx number and hash of the hostname | |||||
tx := generateTx(connIndex, txNumber, t.Size, hostnameHash) | |||||
paramsJSON, err := json.Marshal(map[string]interface{}{"tx": hex.EncodeToString(tx)}) | |||||
if err != nil { | |||||
fmt.Printf("failed to encode params: %v\n", err) | |||||
os.Exit(1) | |||||
} | |||||
rawParamsJSON := json.RawMessage(paramsJSON) | |||||
c.SetWriteDeadline(time.Now().Add(sendTimeout)) | |||||
err = c.WriteJSON(rpctypes.RPCRequest{ | |||||
JSONRPC: "2.0", | |||||
ID: "tm-bench", | |||||
Method: t.BroadcastTxMethod, | |||||
Params: rawParamsJSON, | |||||
}) | |||||
if err != nil { | |||||
err = errors.Wrap(err, | |||||
fmt.Sprintf("txs send failed on connection #%d", connIndex)) | |||||
t.connsBroken[connIndex] = true | |||||
logger.Error(err.Error()) | |||||
return | |||||
} | |||||
// Time added here is 7.13 ns/op, not significant enough to worry about | |||||
if i%20 == 0 { | |||||
if time.Now().After(endTime) { | |||||
// Plus one accounts for sending this tx | |||||
numTxSent = i + 1 | |||||
break | |||||
} | |||||
} | |||||
txNumber++ | |||||
} | |||||
timeToSend := time.Now().Sub(startTime) | |||||
logger.Info(fmt.Sprintf("sent %d transactions", numTxSent), "took", timeToSend) | |||||
if timeToSend < 1*time.Second { | |||||
sleepTime := time.Second - timeToSend | |||||
logger.Debug(fmt.Sprintf("connection #%d is sleeping for %f seconds", connIndex, sleepTime.Seconds())) | |||||
time.Sleep(sleepTime) | |||||
} | |||||
case <-pingsTicker.C: | |||||
// go-rpc server closes the connection in the absence of pings | |||||
c.SetWriteDeadline(time.Now().Add(sendTimeout)) | |||||
if err := c.WriteMessage(websocket.PingMessage, []byte{}); err != nil { | |||||
err = errors.Wrap(err, | |||||
fmt.Sprintf("failed to write ping message on conn #%d", connIndex)) | |||||
logger.Error(err.Error()) | |||||
t.connsBroken[connIndex] = true | |||||
} | |||||
} | |||||
if t.stopped { | |||||
// To cleanly close a connection, a client should send a close | |||||
// frame and wait for the server to close the connection. | |||||
c.SetWriteDeadline(time.Now().Add(sendTimeout)) | |||||
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) | |||||
if err != nil { | |||||
err = errors.Wrap(err, | |||||
fmt.Sprintf("failed to write close message on conn #%d", connIndex)) | |||||
logger.Error(err.Error()) | |||||
t.connsBroken[connIndex] = true | |||||
} | |||||
return | |||||
} | |||||
} | |||||
} | |||||
func connect(host string) (*websocket.Conn, *http.Response, error) { | |||||
u := url.URL{Scheme: "ws", Host: host, Path: "/websocket"} | |||||
return websocket.DefaultDialer.Dial(u.String(), nil) | |||||
} | |||||
func generateTx(connIndex int, txNumber int, txSize int, hostnameHash [md5.Size]byte) []byte { | |||||
tx := make([]byte, txSize) | |||||
binary.PutUvarint(tx[:8], uint64(connIndex)) | |||||
binary.PutUvarint(tx[8:16], uint64(txNumber)) | |||||
copy(tx[16:32], hostnameHash[:16]) | |||||
binary.PutUvarint(tx[32:40], uint64(time.Now().Unix())) | |||||
// 40-* random data | |||||
if _, err := rand.Read(tx[40:]); err != nil { | |||||
panic(errors.Wrap(err, "failed to read random bytes")) | |||||
} | |||||
return tx | |||||
} |
@ -0,0 +1,6 @@ | |||||
FROM alpine:3.6 | |||||
WORKDIR /app | |||||
COPY tm-monitor /app/tm-monitor | |||||
ENTRYPOINT ["./tm-monitor"] |
@ -0,0 +1,12 @@ | |||||
FROM golang:latest | |||||
RUN mkdir -p /go/src/github.com/tendermint/tools/tm-monitor | |||||
WORKDIR /go/src/github.com/tendermint/tools/tm-monitor | |||||
COPY Makefile /go/src/github.com/tendermint/tools/tm-monitor/ | |||||
RUN make get_tools | |||||
COPY . /go/src/github.com/tendermint/tools/tm-monitor | |||||
RUN make get_vendor_deps |
@ -0,0 +1,291 @@ | |||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/btcsuite/btcd" | |||||
packages = ["btcec"] | |||||
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3" | |||||
[[projects]] | |||||
name = "github.com/davecgh/go-spew" | |||||
packages = ["spew"] | |||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76" | |||||
version = "v1.1.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/ebuchman/fail-test" | |||||
packages = ["."] | |||||
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" | |||||
[[projects]] | |||||
name = "github.com/go-kit/kit" | |||||
packages = [ | |||||
"log", | |||||
"log/level", | |||||
"log/term" | |||||
] | |||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71" | |||||
version = "v0.6.0" | |||||
[[projects]] | |||||
name = "github.com/go-logfmt/logfmt" | |||||
packages = ["."] | |||||
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" | |||||
version = "v0.3.0" | |||||
[[projects]] | |||||
name = "github.com/go-stack/stack" | |||||
packages = ["."] | |||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" | |||||
version = "v1.7.0" | |||||
[[projects]] | |||||
name = "github.com/gogo/protobuf" | |||||
packages = [ | |||||
"gogoproto", | |||||
"jsonpb", | |||||
"proto", | |||||
"protoc-gen-gogo/descriptor", | |||||
"sortkeys", | |||||
"types" | |||||
] | |||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
name = "github.com/golang/protobuf" | |||||
packages = [ | |||||
"proto", | |||||
"ptypes", | |||||
"ptypes/any", | |||||
"ptypes/duration", | |||||
"ptypes/timestamp" | |||||
] | |||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/golang/snappy" | |||||
packages = ["."] | |||||
revision = "553a641470496b2327abcac10b36396bd98e45c9" | |||||
[[projects]] | |||||
name = "github.com/gorilla/websocket" | |||||
packages = ["."] | |||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" | |||||
version = "v1.2.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/jmhodges/levigo" | |||||
packages = ["."] | |||||
revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/kr/logfmt" | |||||
packages = ["."] | |||||
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" | |||||
[[projects]] | |||||
name = "github.com/pkg/errors" | |||||
packages = ["."] | |||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" | |||||
version = "v0.8.0" | |||||
[[projects]] | |||||
name = "github.com/pmezard/go-difflib" | |||||
packages = ["difflib"] | |||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/rcrowley/go-metrics" | |||||
packages = ["."] | |||||
revision = "d932a24a8ccb8fcadc993e5c6c58f93dac168294" | |||||
[[projects]] | |||||
name = "github.com/stretchr/testify" | |||||
packages = [ | |||||
"assert", | |||||
"require" | |||||
] | |||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" | |||||
version = "v1.2.1" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/syndtr/goleveldb" | |||||
packages = [ | |||||
"leveldb", | |||||
"leveldb/cache", | |||||
"leveldb/comparer", | |||||
"leveldb/errors", | |||||
"leveldb/filter", | |||||
"leveldb/iterator", | |||||
"leveldb/journal", | |||||
"leveldb/memdb", | |||||
"leveldb/opt", | |||||
"leveldb/storage", | |||||
"leveldb/table", | |||||
"leveldb/util" | |||||
] | |||||
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" | |||||
[[projects]] | |||||
name = "github.com/tendermint/abci" | |||||
packages = [ | |||||
"client", | |||||
"example/code", | |||||
"example/kvstore", | |||||
"types" | |||||
] | |||||
revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f" | |||||
version = "v0.10.3" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/tendermint/ed25519" | |||||
packages = [ | |||||
".", | |||||
"edwards25519", | |||||
"extra25519" | |||||
] | |||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" | |||||
[[projects]] | |||||
name = "github.com/tendermint/go-amino" | |||||
packages = ["."] | |||||
revision = "42246108ff925a457fb709475070a03dfd3e2b5c" | |||||
version = "0.9.6" | |||||
[[projects]] | |||||
name = "github.com/tendermint/go-crypto" | |||||
packages = ["."] | |||||
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19" | |||||
version = "v0.6.2" | |||||
[[projects]] | |||||
name = "github.com/tendermint/tendermint" | |||||
packages = [ | |||||
"config", | |||||
"p2p", | |||||
"p2p/conn", | |||||
"p2p/upnp", | |||||
"proxy", | |||||
"rpc/core/types", | |||||
"rpc/lib/client", | |||||
"rpc/lib/server", | |||||
"rpc/lib/types", | |||||
"state", | |||||
"types" | |||||
] | |||||
revision = "d0beaba7e8a5652506a34b5fab299cc2dc274c02" | |||||
version = "v0.19.0" | |||||
[[projects]] | |||||
name = "github.com/tendermint/tmlibs" | |||||
packages = [ | |||||
"common", | |||||
"db", | |||||
"events", | |||||
"flowrate", | |||||
"log", | |||||
"merkle", | |||||
"pubsub", | |||||
"pubsub/query" | |||||
] | |||||
revision = "737154202faf75c70437f59ba5303f2eb09f5636" | |||||
version = "0.8.2-rc1" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "golang.org/x/crypto" | |||||
packages = [ | |||||
"curve25519", | |||||
"nacl/box", | |||||
"nacl/secretbox", | |||||
"openpgp/armor", | |||||
"openpgp/errors", | |||||
"poly1305", | |||||
"ripemd160", | |||||
"salsa20/salsa" | |||||
] | |||||
revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "golang.org/x/net" | |||||
packages = [ | |||||
"context", | |||||
"http/httpguts", | |||||
"http2", | |||||
"http2/hpack", | |||||
"idna", | |||||
"internal/timeseries", | |||||
"lex/httplex", | |||||
"trace" | |||||
] | |||||
revision = "8d16fa6dc9a85c1cd3ed24ad08ff21cf94f10888" | |||||
[[projects]] | |||||
name = "golang.org/x/text" | |||||
packages = [ | |||||
"collate", | |||||
"collate/build", | |||||
"internal/colltab", | |||||
"internal/gen", | |||||
"internal/tag", | |||||
"internal/triegen", | |||||
"internal/ucd", | |||||
"language", | |||||
"secure/bidirule", | |||||
"transform", | |||||
"unicode/bidi", | |||||
"unicode/cldr", | |||||
"unicode/norm", | |||||
"unicode/rangetable" | |||||
] | |||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" | |||||
version = "v0.3.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "google.golang.org/genproto" | |||||
packages = ["googleapis/rpc/status"] | |||||
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" | |||||
[[projects]] | |||||
name = "google.golang.org/grpc" | |||||
packages = [ | |||||
".", | |||||
"balancer", | |||||
"codes", | |||||
"connectivity", | |||||
"credentials", | |||||
"grpclb/grpc_lb_v1/messages", | |||||
"grpclog", | |||||
"internal", | |||||
"keepalive", | |||||
"metadata", | |||||
"naming", | |||||
"peer", | |||||
"resolver", | |||||
"stats", | |||||
"status", | |||||
"tap", | |||||
"transport" | |||||
] | |||||
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" | |||||
version = "v1.7.5" | |||||
[solve-meta] | |||||
analyzer-name = "dep" | |||||
analyzer-version = 1 | |||||
inputs-digest = "c445a659655eae3fbc1355e7d3431aa7f4b78f2f052ecba723cf8dcefd6350c4" | |||||
solver-name = "gps-cdcl" | |||||
solver-version = 1 |
@ -0,0 +1,58 @@ | |||||
# Gopkg.toml example | |||||
# | |||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html | |||||
# for detailed Gopkg.toml documentation. | |||||
# | |||||
# required = ["github.com/user/thing/cmd/thing"] | |||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] | |||||
# | |||||
# [[constraint]] | |||||
# name = "github.com/user/project" | |||||
# version = "1.0.0" | |||||
# | |||||
# [[constraint]] | |||||
# name = "github.com/user/project2" | |||||
# branch = "dev" | |||||
# source = "github.com/myfork/project2" | |||||
# | |||||
# [[override]] | |||||
# name = "github.com/x/y" | |||||
# version = "2.4.0" | |||||
# | |||||
# [prune] | |||||
# non-go = false | |||||
# go-tests = true | |||||
# unused-packages = true | |||||
[[constraint]] | |||||
name = "github.com/pkg/errors" | |||||
version = "0.8.0" | |||||
[[constraint]] | |||||
branch = "master" | |||||
name = "github.com/rcrowley/go-metrics" | |||||
[[constraint]] | |||||
name = "github.com/stretchr/testify" | |||||
version = "1.2.1" | |||||
[[constraint]] | |||||
name = "github.com/tendermint/go-crypto" | |||||
version = "~0.6.2" | |||||
[[constraint]] | |||||
name = "github.com/tendermint/go-amino" | |||||
version = "~0.9.6" | |||||
[[constraint]] | |||||
name = "github.com/tendermint/tendermint" | |||||
version = "0.19.0" | |||||
[[override]] | |||||
name = "github.com/tendermint/tmlibs" | |||||
version = "~0.8.2-rc1" | |||||
[prune] | |||||
go-tests = true | |||||
unused-packages = true |
@ -0,0 +1,204 @@ | |||||
Tendermint Monitor | |||||
Copyright 2017 Tendermint | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
http://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
APPENDIX: How to apply the Apache License to your work. | |||||
To apply the Apache License to your work, attach the following | |||||
boilerplate notice, with the fields enclosed by brackets "[]" | |||||
replaced with your own identifying information. (Don't include | |||||
the brackets!) The text should be enclosed in the appropriate | |||||
comment syntax for the file format. We also recommend that a | |||||
file or class name and description of purpose be included on the | |||||
same "printed page" as the copyright notice for easier | |||||
identification within third-party archives. | |||||
Copyright [yyyy] [name of copyright owner] | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,116 @@ | |||||
DIST_DIRS := find * -type d -exec | |||||
VERSION := $(shell perl -ne '/^var version.*"([^"]+)".*$$/ && print "v$$1\n"' main.go) | |||||
GOTOOLS = \ | |||||
github.com/mitchellh/gox \ | |||||
github.com/golang/dep/cmd/dep \ | |||||
gopkg.in/alecthomas/gometalinter.v2 | |||||
PACKAGES=$(shell go list ./... | grep -v '/vendor/') | |||||
all: check get_vendor_deps build test install metalinter | |||||
check: check_tools | |||||
######################################## | |||||
### Tools & dependencies | |||||
check_tools: | |||||
@# https://stackoverflow.com/a/25668869 | |||||
@echo "Found tools: $(foreach tool,$(GOTOOLS_CHECK),\ | |||||
$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))" | |||||
get_tools: | |||||
@echo "--> Installing tools" | |||||
go get -u -v $(GOTOOLS) | |||||
@gometalinter.v2 --install | |||||
update_tools: | |||||
@echo "--> Updating tools" | |||||
@go get -u $(GOTOOLS) | |||||
get_vendor_deps: | |||||
@rm -rf vendor/ | |||||
@echo "--> Running dep ensure" | |||||
@dep ensure | |||||
######################################## | |||||
### Build | |||||
build: | |||||
@go build | |||||
install: | |||||
@go install | |||||
test: | |||||
@go test -race $(PACKAGES) | |||||
build-all: check_tools | |||||
rm -rf ./dist | |||||
gox -verbose \ | |||||
-ldflags "-s -w" \ | |||||
-arch="amd64 386 arm arm64" \ | |||||
-os="linux darwin windows freebsd" \ | |||||
-osarch="!darwin/arm !darwin/arm64" \ | |||||
-output="dist/{{.OS}}-{{.Arch}}/{{.Dir}}" . | |||||
dist: build-all | |||||
cd dist && \ | |||||
$(DIST_DIRS) cp ../LICENSE {} \; && \ | |||||
$(DIST_DIRS) tar -zcf tm-monitor-${VERSION}-{}.tar.gz {} \; && \ | |||||
shasum -a256 ./*.tar.gz > "./tm-monitor_${VERSION}_SHA256SUMS" && \ | |||||
cd .. | |||||
######################################## | |||||
### Docker | |||||
build-docker: | |||||
rm -f ./tm-monitor | |||||
docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/tools/tm-monitor" -w "/go/src/github.com/tendermint/tools/tm-monitor" -e "CGO_ENABLED=0" golang:alpine go build -ldflags "-s -w" -o tm-monitor | |||||
docker build -t "tendermint/monitor" . | |||||
clean: | |||||
rm -f ./tm-monitor | |||||
rm -rf ./dist | |||||
######################################## | |||||
### Formatting, linting, and vetting | |||||
fmt: | |||||
@go fmt ./... | |||||
metalinter: | |||||
@echo "==> Running linter" | |||||
gometalinter.v2 --vendor --deadline=600s --disable-all \ | |||||
--enable=maligned \ | |||||
--enable=deadcode \ | |||||
--enable=goconst \ | |||||
--enable=goimports \ | |||||
--enable=gosimple \ | |||||
--enable=ineffassign \ | |||||
--enable=megacheck \ | |||||
--enable=misspell \ | |||||
--enable=staticcheck \ | |||||
--enable=safesql \ | |||||
--enable=structcheck \ | |||||
--enable=unconvert \ | |||||
--enable=unused \ | |||||
--enable=varcheck \ | |||||
--enable=vetshadow \ | |||||
./... | |||||
#--enable=gas \ | |||||
#--enable=dupl \ | |||||
#--enable=errcheck \ | |||||
#--enable=gocyclo \ | |||||
#--enable=golint \ <== comments on anything exported | |||||
#--enable=gotype \ | |||||
#--enable=interfacer \ | |||||
#--enable=unparam \ | |||||
#--enable=vet \ | |||||
metalinter_all: | |||||
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./... | |||||
# To avoid unintended conflicts with file names, always add to .PHONY | |||||
# unless there is a reason not to. | |||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html | |||||
.PHONY: check check_tools get_tools update_tools get_vendor_deps build install test build-all dist fmt metalinter metalinter_all build-docker clean |
@ -0,0 +1,77 @@ | |||||
# tm-monitor | |||||
Tendermint blockchain monitoring tool; watches over one or more nodes, | |||||
collecting and providing various statistics to the user: | |||||
- https://github.com/tendermint/tools/tree/master/tm-monitor | |||||
## Quick Start | |||||
### Docker | |||||
Assuming your application is running in another container with the name | |||||
`app`: | |||||
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init | |||||
docker run -it --rm -v "/tmp:/tendermint" -p "26657:26657" --name=tm --link=app tendermint/tendermint node --proxy_app=tcp://app:26658 | |||||
docker run -it --rm -p "26670:26670" --link=tm tendermint/monitor tm:26657 | |||||
If you don't have an application yet, but still want to try monitor out, | |||||
use `kvstore`: | |||||
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init | |||||
docker run -it --rm -v "/tmp:/tendermint" -p "26657:26657" --name=tm tendermint/tendermint node --proxy_app=kvstore | |||||
docker run -it --rm -p "26670:26670" --link=tm tendermint/monitor tm:26657 | |||||
### Using Binaries | |||||
[Install Tendermint](https://github.com/tendermint/tendermint#install) | |||||
then run: | |||||
tendermint init | |||||
tendermint node --proxy_app=kvstore | |||||
tm-monitor localhost:26657 | |||||
with the last command being in a seperate window. | |||||
## Usage | |||||
tm-monitor [-v] [-no-ton] [-listen-addr="tcp://0.0.0.0:26670"] [endpoints] | |||||
Examples: | |||||
# monitor single instance | |||||
tm-monitor localhost:26657 | |||||
# monitor a few instances by providing comma-separated list of RPC endpoints | |||||
tm-monitor host1:26657,host2:26657 | |||||
Flags: | |||||
-listen-addr string | |||||
HTTP and Websocket server listen address (default "tcp://0.0.0.0:26670") | |||||
-no-ton | |||||
Do not show ton (table of nodes) | |||||
-v verbose logging | |||||
### RPC UI | |||||
Run `tm-monitor` and visit http://localhost:26670 You should see the | |||||
list of the available RPC endpoints: | |||||
http://localhost:26670/status | |||||
http://localhost:26670/status/network | |||||
http://localhost:26670/monitor?endpoint=_ | |||||
http://localhost:26670/status/node?name=_ | |||||
http://localhost:26670/unmonitor?endpoint=_ | |||||
The API is available as GET requests with URI encoded parameters, or as | |||||
JSONRPC POST requests. The JSONRPC methods are also exposed over | |||||
websocket. | |||||
## Development | |||||
make get_tools | |||||
make get_vendor_deps | |||||
make test |
@ -0,0 +1,298 @@ | |||||
// eventmeter - generic system to subscribe to events and record their frequency. | |||||
package eventmeter | |||||
import ( | |||||
"context" | |||||
"encoding/json" | |||||
"fmt" | |||||
"sync" | |||||
"time" | |||||
metrics "github.com/rcrowley/go-metrics" | |||||
client "github.com/tendermint/tendermint/rpc/lib/client" | |||||
"github.com/tendermint/tmlibs/events" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
const ( | |||||
// Get ping/pong latency and call LatencyCallbackFunc with this period. | |||||
latencyPeriod = 1 * time.Second | |||||
// Check if the WS client is connected every | |||||
connectionCheckPeriod = 100 * time.Millisecond | |||||
) | |||||
// EventMetric exposes metrics for an event. | |||||
type EventMetric struct { | |||||
ID string `json:"id"` | |||||
Started time.Time `json:"start_time"` | |||||
LastHeard time.Time `json:"last_heard"` | |||||
MinDuration int64 `json:"min_duration"` | |||||
MaxDuration int64 `json:"max_duration"` | |||||
// tracks event count and rate | |||||
meter metrics.Meter | |||||
// filled in from the Meter | |||||
Count int64 `json:"count"` | |||||
Rate1 float64 `json:"rate_1" amino:"unsafe"` | |||||
Rate5 float64 `json:"rate_5" amino:"unsafe"` | |||||
Rate15 float64 `json:"rate_15" amino:"unsafe"` | |||||
RateMean float64 `json:"rate_mean" amino:"unsafe"` | |||||
// so the event can have effects in the eventmeter's consumer. runs in a go | |||||
// routine. | |||||
callback EventCallbackFunc | |||||
} | |||||
func (metric *EventMetric) Copy() *EventMetric { | |||||
metricCopy := *metric | |||||
metricCopy.meter = metric.meter.Snapshot() | |||||
return &metricCopy | |||||
} | |||||
// called on GetMetric | |||||
func (metric *EventMetric) fillMetric() *EventMetric { | |||||
metric.Count = metric.meter.Count() | |||||
metric.Rate1 = metric.meter.Rate1() | |||||
metric.Rate5 = metric.meter.Rate5() | |||||
metric.Rate15 = metric.meter.Rate15() | |||||
metric.RateMean = metric.meter.RateMean() | |||||
return metric | |||||
} | |||||
// EventCallbackFunc is a closure to enable side effects from receiving an | |||||
// event. | |||||
type EventCallbackFunc func(em *EventMetric, data interface{}) | |||||
// EventUnmarshalFunc is a closure to get the query and data out of the raw | |||||
// JSON received over the RPC WebSocket. | |||||
type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error) | |||||
// LatencyCallbackFunc is a closure to enable side effects from receiving a latency. | |||||
type LatencyCallbackFunc func(meanLatencyNanoSeconds float64) | |||||
// DisconnectCallbackFunc is a closure to notify a consumer that the connection | |||||
// has died. | |||||
type DisconnectCallbackFunc func() | |||||
// EventMeter tracks events, reports latency and disconnects. | |||||
type EventMeter struct { | |||||
wsc *client.WSClient | |||||
mtx sync.Mutex | |||||
queryToMetricMap map[string]*EventMetric | |||||
unmarshalEvent EventUnmarshalFunc | |||||
latencyCallback LatencyCallbackFunc | |||||
disconnectCallback DisconnectCallbackFunc | |||||
subscribed bool | |||||
quit chan struct{} | |||||
logger log.Logger | |||||
} | |||||
func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter { | |||||
return &EventMeter{ | |||||
wsc: client.NewWSClient(addr, "/websocket", client.PingPeriod(1*time.Second)), | |||||
queryToMetricMap: make(map[string]*EventMetric), | |||||
unmarshalEvent: unmarshalEvent, | |||||
logger: log.NewNopLogger(), | |||||
} | |||||
} | |||||
// SetLogger lets you set your own logger. | |||||
func (em *EventMeter) SetLogger(l log.Logger) { | |||||
em.logger = l | |||||
em.wsc.SetLogger(l.With("module", "rpcclient")) | |||||
} | |||||
// String returns a string representation of event meter. | |||||
func (em *EventMeter) String() string { | |||||
return em.wsc.Address | |||||
} | |||||
// Start boots up event meter. | |||||
func (em *EventMeter) Start() error { | |||||
if err := em.wsc.Start(); err != nil { | |||||
return err | |||||
} | |||||
em.quit = make(chan struct{}) | |||||
go em.receiveRoutine() | |||||
go em.disconnectRoutine() | |||||
err := em.subscribe() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
em.subscribed = true | |||||
return nil | |||||
} | |||||
// Stop stops event meter. | |||||
func (em *EventMeter) Stop() { | |||||
close(em.quit) | |||||
if em.wsc.IsRunning() { | |||||
em.wsc.Stop() | |||||
} | |||||
} | |||||
// Subscribe for the given query. Callback function will be called upon | |||||
// receiving an event. | |||||
func (em *EventMeter) Subscribe(query string, cb EventCallbackFunc) error { | |||||
em.mtx.Lock() | |||||
defer em.mtx.Unlock() | |||||
if err := em.wsc.Subscribe(context.TODO(), query); err != nil { | |||||
return err | |||||
} | |||||
metric := &EventMetric{ | |||||
meter: metrics.NewMeter(), | |||||
callback: cb, | |||||
} | |||||
em.queryToMetricMap[query] = metric | |||||
return nil | |||||
} | |||||
// Unsubscribe from the given query. | |||||
func (em *EventMeter) Unsubscribe(query string) error { | |||||
em.mtx.Lock() | |||||
defer em.mtx.Unlock() | |||||
if err := em.wsc.Unsubscribe(context.TODO(), query); err != nil { | |||||
return err | |||||
} | |||||
return nil | |||||
} | |||||
// GetMetric fills in the latest data for an query and return a copy. | |||||
func (em *EventMeter) GetMetric(query string) (*EventMetric, error) { | |||||
em.mtx.Lock() | |||||
defer em.mtx.Unlock() | |||||
metric, ok := em.queryToMetricMap[query] | |||||
if !ok { | |||||
return nil, fmt.Errorf("unknown query: %s", query) | |||||
} | |||||
return metric.fillMetric().Copy(), nil | |||||
} | |||||
// RegisterLatencyCallback allows you to set latency callback. | |||||
func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) { | |||||
em.mtx.Lock() | |||||
defer em.mtx.Unlock() | |||||
em.latencyCallback = f | |||||
} | |||||
// RegisterDisconnectCallback allows you to set disconnect callback. | |||||
func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) { | |||||
em.mtx.Lock() | |||||
defer em.mtx.Unlock() | |||||
em.disconnectCallback = f | |||||
} | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Private | |||||
func (em *EventMeter) subscribe() error { | |||||
for query, _ := range em.queryToMetricMap { | |||||
if err := em.wsc.Subscribe(context.TODO(), query); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
func (em *EventMeter) receiveRoutine() { | |||||
latencyTicker := time.NewTicker(latencyPeriod) | |||||
for { | |||||
select { | |||||
case resp := <-em.wsc.ResponsesCh: | |||||
if resp.Error != nil { | |||||
em.logger.Error("expected some event, got error", "err", resp.Error.Error()) | |||||
continue | |||||
} | |||||
query, data, err := em.unmarshalEvent(resp.Result) | |||||
if err != nil { | |||||
em.logger.Error("failed to unmarshal event", "err", err) | |||||
continue | |||||
} | |||||
if query != "" { // FIXME how can it be an empty string? | |||||
em.updateMetric(query, data) | |||||
} | |||||
case <-latencyTicker.C: | |||||
if em.wsc.IsActive() { | |||||
em.callLatencyCallback(em.wsc.PingPongLatencyTimer.Mean()) | |||||
} | |||||
case <-em.wsc.Quit(): | |||||
return | |||||
case <-em.quit: | |||||
return | |||||
} | |||||
} | |||||
} | |||||
func (em *EventMeter) disconnectRoutine() { | |||||
ticker := time.NewTicker(connectionCheckPeriod) | |||||
for { | |||||
select { | |||||
case <-ticker.C: | |||||
if em.wsc.IsReconnecting() && em.subscribed { // notify user about disconnect only once | |||||
em.callDisconnectCallback() | |||||
em.subscribed = false | |||||
} else if !em.wsc.IsReconnecting() && !em.subscribed { // resubscribe | |||||
em.subscribe() | |||||
em.subscribed = true | |||||
} | |||||
case <-em.wsc.Quit(): | |||||
return | |||||
case <-em.quit: | |||||
return | |||||
} | |||||
} | |||||
} | |||||
func (em *EventMeter) updateMetric(query string, data events.EventData) { | |||||
em.mtx.Lock() | |||||
defer em.mtx.Unlock() | |||||
metric, ok := em.queryToMetricMap[query] | |||||
if !ok { | |||||
// we already unsubscribed, or got an unexpected query | |||||
return | |||||
} | |||||
last := metric.LastHeard | |||||
metric.LastHeard = time.Now() | |||||
metric.meter.Mark(1) | |||||
dur := int64(metric.LastHeard.Sub(last)) | |||||
if dur < metric.MinDuration { | |||||
metric.MinDuration = dur | |||||
} | |||||
if !last.IsZero() && dur > metric.MaxDuration { | |||||
metric.MaxDuration = dur | |||||
} | |||||
if metric.callback != nil { | |||||
go metric.callback(metric.Copy(), data) | |||||
} | |||||
} | |||||
func (em *EventMeter) callDisconnectCallback() { | |||||
em.mtx.Lock() | |||||
if em.disconnectCallback != nil { | |||||
go em.disconnectCallback() | |||||
} | |||||
em.mtx.Unlock() | |||||
} | |||||
func (em *EventMeter) callLatencyCallback(meanLatencyNanoSeconds float64) { | |||||
em.mtx.Lock() | |||||
if em.latencyCallback != nil { | |||||
go em.latencyCallback(meanLatencyNanoSeconds) | |||||
} | |||||
em.mtx.Unlock() | |||||
} |
@ -0,0 +1,88 @@ | |||||
package main | |||||
import ( | |||||
"flag" | |||||
"fmt" | |||||
"os" | |||||
"strings" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
"github.com/tendermint/tmlibs/log" | |||||
monitor "github.com/tendermint/tools/tm-monitor/monitor" | |||||
) | |||||
var version = "0.4.0" | |||||
var logger = log.NewNopLogger() | |||||
func main() { | |||||
var listenAddr string | |||||
var noton bool | |||||
flag.StringVar(&listenAddr, "listen-addr", "tcp://0.0.0.0:26670", "HTTP and Websocket server listen address") | |||||
flag.BoolVar(¬on, "no-ton", false, "Do not show ton (table of nodes)") | |||||
flag.Usage = func() { | |||||
fmt.Println(`Tendermint monitor watches over one or more Tendermint core | |||||
applications, collecting and providing various statistics to the user. | |||||
Usage: | |||||
tm-monitor [-no-ton] [-listen-addr="tcp://0.0.0.0:26670"] [endpoints] | |||||
Examples: | |||||
# monitor single instance | |||||
tm-monitor localhost:26657 | |||||
# monitor a few instances by providing comma-separated list of RPC endpoints | |||||
tm-monitor host1:26657,host2:26657`) | |||||
fmt.Println("Flags:") | |||||
flag.PrintDefaults() | |||||
} | |||||
flag.Parse() | |||||
if flag.NArg() == 0 { | |||||
flag.Usage() | |||||
os.Exit(1) | |||||
} | |||||
if noton { | |||||
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||||
} | |||||
m := startMonitor(flag.Arg(0)) | |||||
startRPC(listenAddr, m, logger) | |||||
var ton *Ton | |||||
if !noton { | |||||
ton = NewTon(m) | |||||
ton.Start() | |||||
} | |||||
cmn.TrapSignal(func() { | |||||
if !noton { | |||||
ton.Stop() | |||||
} | |||||
m.Stop() | |||||
}) | |||||
} | |||||
func startMonitor(endpoints string) *monitor.Monitor { | |||||
m := monitor.NewMonitor() | |||||
m.SetLogger(logger.With("component", "monitor")) | |||||
for _, e := range strings.Split(endpoints, ",") { | |||||
n := monitor.NewNode(e) | |||||
n.SetLogger(logger.With("node", e)) | |||||
if err := m.Monitor(n); err != nil { | |||||
panic(err) | |||||
} | |||||
} | |||||
if err := m.Start(); err != nil { | |||||
panic(err) | |||||
} | |||||
return m | |||||
} |
@ -0,0 +1,69 @@ | |||||
package mock | |||||
import ( | |||||
stdlog "log" | |||||
"reflect" | |||||
"github.com/tendermint/tmlibs/log" | |||||
em "github.com/tendermint/tools/tm-monitor/eventmeter" | |||||
"github.com/tendermint/go-amino" | |||||
) | |||||
type EventMeter struct { | |||||
latencyCallback em.LatencyCallbackFunc | |||||
disconnectCallback em.DisconnectCallbackFunc | |||||
eventCallback em.EventCallbackFunc | |||||
} | |||||
func (e *EventMeter) Start() error { return nil } | |||||
func (e *EventMeter) Stop() {} | |||||
func (e *EventMeter) SetLogger(l log.Logger) {} | |||||
func (e *EventMeter) RegisterLatencyCallback(cb em.LatencyCallbackFunc) { e.latencyCallback = cb } | |||||
func (e *EventMeter) RegisterDisconnectCallback(cb em.DisconnectCallbackFunc) { | |||||
e.disconnectCallback = cb | |||||
} | |||||
func (e *EventMeter) Subscribe(query string, cb em.EventCallbackFunc) error { | |||||
e.eventCallback = cb | |||||
return nil | |||||
} | |||||
func (e *EventMeter) Unsubscribe(query string) error { | |||||
e.eventCallback = nil | |||||
return nil | |||||
} | |||||
func (e *EventMeter) Call(callback string, args ...interface{}) { | |||||
switch callback { | |||||
case "latencyCallback": | |||||
e.latencyCallback(args[0].(float64)) | |||||
case "disconnectCallback": | |||||
e.disconnectCallback() | |||||
case "eventCallback": | |||||
e.eventCallback(args[0].(*em.EventMetric), args[1]) | |||||
} | |||||
} | |||||
type RpcClient struct { | |||||
Stubs map[string]interface{} | |||||
cdc *amino.Codec | |||||
} | |||||
func (c *RpcClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { | |||||
s, ok := c.Stubs[method] | |||||
if !ok { | |||||
stdlog.Fatalf("Call to %s, but no stub is defined for it", method) | |||||
} | |||||
rv, rt := reflect.ValueOf(result), reflect.TypeOf(result) | |||||
rv, rt = rv.Elem(), rt.Elem() | |||||
rv.Set(reflect.ValueOf(s)) | |||||
return s, nil | |||||
} | |||||
func (c *RpcClient) Codec() *amino.Codec { | |||||
return c.cdc | |||||
} | |||||
func (c *RpcClient) SetCodec(cdc *amino.Codec) { | |||||
c.cdc = cdc | |||||
} |
@ -0,0 +1,251 @@ | |||||
package monitor | |||||
import ( | |||||
"fmt" | |||||
"math/rand" | |||||
"sync" | |||||
"time" | |||||
"github.com/pkg/errors" | |||||
tmtypes "github.com/tendermint/tendermint/types" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
// waiting more than this many seconds for a block means we're unhealthy | |||||
const nodeLivenessTimeout = 5 * time.Second | |||||
// Monitor keeps track of the nodes and updates common statistics upon | |||||
// receiving new events from nodes. | |||||
// | |||||
// Common statistics is stored in Network struct. | |||||
type Monitor struct { | |||||
mtx sync.Mutex | |||||
Nodes []*Node | |||||
Network *Network | |||||
monitorQuit chan struct{} // monitor exitting | |||||
nodeQuit map[string]chan struct{} // node is being stopped and removed from under the monitor | |||||
recalculateNetworkUptimeEvery time.Duration | |||||
numValidatorsUpdateInterval time.Duration | |||||
logger log.Logger | |||||
} | |||||
// NewMonitor creates new instance of a Monitor. You can provide options to | |||||
// change some default values. | |||||
// | |||||
// Example: | |||||
// NewMonitor(monitor.SetNumValidatorsUpdateInterval(1 * time.Second)) | |||||
func NewMonitor(options ...func(*Monitor)) *Monitor { | |||||
m := &Monitor{ | |||||
Nodes: make([]*Node, 0), | |||||
Network: NewNetwork(), | |||||
monitorQuit: make(chan struct{}), | |||||
nodeQuit: make(map[string]chan struct{}), | |||||
recalculateNetworkUptimeEvery: 10 * time.Second, | |||||
numValidatorsUpdateInterval: 5 * time.Second, | |||||
logger: log.NewNopLogger(), | |||||
} | |||||
for _, option := range options { | |||||
option(m) | |||||
} | |||||
return m | |||||
} | |||||
// RecalculateNetworkUptimeEvery lets you change network uptime update interval. | |||||
func RecalculateNetworkUptimeEvery(d time.Duration) func(m *Monitor) { | |||||
return func(m *Monitor) { | |||||
m.recalculateNetworkUptimeEvery = d | |||||
} | |||||
} | |||||
// SetNumValidatorsUpdateInterval lets you change num validators update interval. | |||||
func SetNumValidatorsUpdateInterval(d time.Duration) func(m *Monitor) { | |||||
return func(m *Monitor) { | |||||
m.numValidatorsUpdateInterval = d | |||||
} | |||||
} | |||||
// SetLogger lets you set your own logger | |||||
func (m *Monitor) SetLogger(l log.Logger) { | |||||
m.logger = l | |||||
} | |||||
// Monitor begins to monitor the node `n`. The node will be started and added | |||||
// to the monitor. | |||||
func (m *Monitor) Monitor(n *Node) error { | |||||
m.mtx.Lock() | |||||
m.Nodes = append(m.Nodes, n) | |||||
m.mtx.Unlock() | |||||
blockCh := make(chan tmtypes.Header, 10) | |||||
n.SendBlocksTo(blockCh) | |||||
blockLatencyCh := make(chan float64, 10) | |||||
n.SendBlockLatenciesTo(blockLatencyCh) | |||||
disconnectCh := make(chan bool, 10) | |||||
n.NotifyAboutDisconnects(disconnectCh) | |||||
if err := n.Start(); err != nil { | |||||
return err | |||||
} | |||||
m.Network.NewNode(n.Name) | |||||
m.nodeQuit[n.Name] = make(chan struct{}) | |||||
go m.listen(n.Name, blockCh, blockLatencyCh, disconnectCh, m.nodeQuit[n.Name]) | |||||
return nil | |||||
} | |||||
// Unmonitor stops monitoring node `n`. The node will be stopped and removed | |||||
// from the monitor. | |||||
func (m *Monitor) Unmonitor(n *Node) { | |||||
m.Network.NodeDeleted(n.Name) | |||||
n.Stop() | |||||
close(m.nodeQuit[n.Name]) | |||||
delete(m.nodeQuit, n.Name) | |||||
i, _ := m.NodeByName(n.Name) | |||||
m.mtx.Lock() | |||||
m.Nodes[i] = m.Nodes[len(m.Nodes)-1] | |||||
m.Nodes = m.Nodes[:len(m.Nodes)-1] | |||||
m.mtx.Unlock() | |||||
} | |||||
// NodeByName returns the node and its index if such node exists within the | |||||
// monitor. Otherwise, -1 and nil are returned. | |||||
func (m *Monitor) NodeByName(name string) (index int, node *Node) { | |||||
m.mtx.Lock() | |||||
defer m.mtx.Unlock() | |||||
for i, n := range m.Nodes { | |||||
if name == n.Name { | |||||
return i, n | |||||
} | |||||
} | |||||
return -1, nil | |||||
} | |||||
// NodeIsOnline is called when connection to the node is restored. | |||||
// Must be safe to call multiple times. | |||||
func (m *Monitor) NodeIsOnline(name string) { | |||||
_, node := m.NodeByName(name) | |||||
if nil != node { | |||||
if online, ok := m.Network.nodeStatusMap[name]; ok && online { | |||||
m.mtx.Lock() | |||||
node.Online = online | |||||
m.mtx.Unlock() | |||||
} | |||||
} | |||||
} | |||||
// Start starts the monitor's routines: recalculating network uptime and | |||||
// updating number of validators. | |||||
func (m *Monitor) Start() error { | |||||
go m.recalculateNetworkUptimeLoop() | |||||
go m.updateNumValidatorLoop() | |||||
return nil | |||||
} | |||||
// Stop stops the monitor's routines. | |||||
func (m *Monitor) Stop() { | |||||
close(m.monitorQuit) | |||||
for _, n := range m.Nodes { | |||||
m.Unmonitor(n) | |||||
} | |||||
} | |||||
// main loop where we listen for events from the node | |||||
func (m *Monitor) listen(nodeName string, blockCh <-chan tmtypes.Header, blockLatencyCh <-chan float64, disconnectCh <-chan bool, quit <-chan struct{}) { | |||||
logger := m.logger.With("node", nodeName) | |||||
for { | |||||
select { | |||||
case <-quit: | |||||
return | |||||
case b := <-blockCh: | |||||
m.Network.NewBlock(b) | |||||
m.Network.NodeIsOnline(nodeName) | |||||
m.NodeIsOnline(nodeName) | |||||
case l := <-blockLatencyCh: | |||||
m.Network.NewBlockLatency(l) | |||||
m.Network.NodeIsOnline(nodeName) | |||||
m.NodeIsOnline(nodeName) | |||||
case disconnected := <-disconnectCh: | |||||
if disconnected { | |||||
m.Network.NodeIsDown(nodeName) | |||||
} else { | |||||
m.Network.NodeIsOnline(nodeName) | |||||
m.NodeIsOnline(nodeName) | |||||
} | |||||
case <-time.After(nodeLivenessTimeout): | |||||
logger.Info("event", fmt.Sprintf("node was not responding for %v", nodeLivenessTimeout)) | |||||
m.Network.NodeIsDown(nodeName) | |||||
} | |||||
} | |||||
} | |||||
// recalculateNetworkUptimeLoop every N seconds. | |||||
func (m *Monitor) recalculateNetworkUptimeLoop() { | |||||
for { | |||||
select { | |||||
case <-m.monitorQuit: | |||||
return | |||||
case <-time.After(m.recalculateNetworkUptimeEvery): | |||||
m.Network.RecalculateUptime() | |||||
} | |||||
} | |||||
} | |||||
// updateNumValidatorLoop sends a request to a random node once every N seconds, | |||||
// which in turn makes an RPC call to get the latest validators. | |||||
func (m *Monitor) updateNumValidatorLoop() { | |||||
rand.Seed(time.Now().Unix()) | |||||
var height int64 | |||||
var num int | |||||
var err error | |||||
for { | |||||
m.mtx.Lock() | |||||
nodesCount := len(m.Nodes) | |||||
m.mtx.Unlock() | |||||
if 0 == nodesCount { | |||||
time.Sleep(m.numValidatorsUpdateInterval) | |||||
continue | |||||
} | |||||
randomNodeIndex := rand.Intn(nodesCount) | |||||
select { | |||||
case <-m.monitorQuit: | |||||
return | |||||
case <-time.After(m.numValidatorsUpdateInterval): | |||||
i := 0 | |||||
m.mtx.Lock() | |||||
for _, n := range m.Nodes { | |||||
if i == randomNodeIndex { | |||||
height, num, err = n.NumValidators() | |||||
if err != nil { | |||||
m.logger.Info("err", errors.Wrap(err, "update num validators failed")) | |||||
} | |||||
break | |||||
} | |||||
i++ | |||||
} | |||||
m.mtx.Unlock() | |||||
m.Network.UpdateNumValidatorsForHeight(num, height) | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,72 @@ | |||||
package monitor_test | |||||
import ( | |||||
"testing" | |||||
"time" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/stretchr/testify/require" | |||||
crypto "github.com/tendermint/go-crypto" | |||||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||||
tmtypes "github.com/tendermint/tendermint/types" | |||||
mock "github.com/tendermint/tools/tm-monitor/mock" | |||||
monitor "github.com/tendermint/tools/tm-monitor/monitor" | |||||
"github.com/tendermint/go-amino" | |||||
) | |||||
func TestMonitorUpdatesNumberOfValidators(t *testing.T) { | |||||
m := startMonitor(t) | |||||
defer m.Stop() | |||||
n, _ := createValidatorNode(t) | |||||
m.Monitor(n) | |||||
assert.Equal(t, 1, m.Network.NumNodesMonitored) | |||||
assert.Equal(t, 1, m.Network.NumNodesMonitoredOnline) | |||||
time.Sleep(1 * time.Second) | |||||
// DATA RACE | |||||
// assert.Equal(t, 1, m.Network.NumValidators()) | |||||
} | |||||
func TestMonitorRecalculatesNetworkUptime(t *testing.T) { | |||||
m := startMonitor(t) | |||||
defer m.Stop() | |||||
assert.Equal(t, 100.0, m.Network.Uptime()) | |||||
n, _ := createValidatorNode(t) | |||||
m.Monitor(n) | |||||
m.Network.NodeIsDown(n.Name) // simulate node failure | |||||
time.Sleep(200 * time.Millisecond) | |||||
m.Network.NodeIsOnline(n.Name) | |||||
time.Sleep(1 * time.Second) | |||||
assert.True(t, m.Network.Uptime() < 100.0, "Uptime should be less than 100%") | |||||
} | |||||
func startMonitor(t *testing.T) *monitor.Monitor { | |||||
m := monitor.NewMonitor( | |||||
monitor.SetNumValidatorsUpdateInterval(200*time.Millisecond), | |||||
monitor.RecalculateNetworkUptimeEvery(200*time.Millisecond), | |||||
) | |||||
err := m.Start() | |||||
require.Nil(t, err) | |||||
return m | |||||
} | |||||
func createValidatorNode(t *testing.T) (n *monitor.Node, emMock *mock.EventMeter) { | |||||
emMock = &mock.EventMeter{} | |||||
stubs := make(map[string]interface{}) | |||||
pubKey := crypto.GenPrivKeyEd25519().PubKey() | |||||
stubs["validators"] = ctypes.ResultValidators{BlockHeight: blockHeight, Validators: []*tmtypes.Validator{tmtypes.NewValidator(pubKey, 0)}} | |||||
stubs["status"] = ctypes.ResultStatus{ValidatorInfo: ctypes.ValidatorInfo{PubKey: pubKey}} | |||||
cdc := amino.NewCodec() | |||||
rpcClientMock := &mock.RpcClient{Stubs: stubs} | |||||
rpcClientMock.SetCodec(cdc) | |||||
n = monitor.NewNodeWithEventMeterAndRpcClient("tcp://127.0.0.1:26657", emMock, rpcClientMock) | |||||
return | |||||
} |
@ -0,0 +1,199 @@ | |||||
package monitor | |||||
import ( | |||||
"sync" | |||||
"time" | |||||
metrics "github.com/rcrowley/go-metrics" | |||||
tmtypes "github.com/tendermint/tendermint/types" | |||||
) | |||||
// UptimeData stores data for how long network has been running. | |||||
type UptimeData struct { | |||||
StartTime time.Time `json:"start_time"` | |||||
Uptime float64 `json:"uptime" amino:"unsafe"` // percentage of time we've been healthy, ever | |||||
totalDownTime time.Duration // total downtime (only updated when we come back online) | |||||
wentDown time.Time | |||||
} | |||||
// Health describes the health of the network. Note that this applies only to | |||||
// the observed nodes, and not to the entire cluster, which may consist of | |||||
// thousands of machines. It may change in the future. | |||||
type Health int | |||||
const ( | |||||
// FullHealth means all nodes online, synced, validators making blocks | |||||
FullHealth = Health(0) | |||||
// ModerateHealth means we're making blocks | |||||
ModerateHealth = Health(1) | |||||
// Dead means we're not making blocks due to all validators freezing or crashing | |||||
Dead = Health(2) | |||||
) | |||||
// Common statistics for network of nodes | |||||
type Network struct { | |||||
Height int64 `json:"height"` | |||||
AvgBlockTime float64 `json:"avg_block_time" amino:"unsafe"` // ms (avg over last minute) | |||||
blockTimeMeter metrics.Meter | |||||
AvgTxThroughput float64 `json:"avg_tx_throughput" amino:"unsafe"` // tx/s (avg over last minute) | |||||
txThroughputMeter metrics.Meter | |||||
AvgBlockLatency float64 `json:"avg_block_latency" amino:"unsafe"` // ms (avg over last minute) | |||||
blockLatencyMeter metrics.Meter | |||||
NumValidators int `json:"num_validators"` | |||||
NumNodesMonitored int `json:"num_nodes_monitored"` | |||||
NumNodesMonitoredOnline int `json:"num_nodes_monitored_online"` | |||||
Health Health `json:"health"` | |||||
UptimeData *UptimeData `json:"uptime_data"` | |||||
nodeStatusMap map[string]bool | |||||
mu sync.Mutex | |||||
} | |||||
func NewNetwork() *Network { | |||||
return &Network{ | |||||
blockTimeMeter: metrics.NewMeter(), | |||||
txThroughputMeter: metrics.NewMeter(), | |||||
blockLatencyMeter: metrics.NewMeter(), | |||||
Health: FullHealth, | |||||
UptimeData: &UptimeData{ | |||||
StartTime: time.Now(), | |||||
Uptime: 100.0, | |||||
}, | |||||
nodeStatusMap: make(map[string]bool), | |||||
} | |||||
} | |||||
func (n *Network) NewBlock(b tmtypes.Header) { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
if n.Height >= b.Height { | |||||
return | |||||
} | |||||
n.Height = b.Height | |||||
n.blockTimeMeter.Mark(1) | |||||
if n.blockTimeMeter.Rate1() > 0.0 { | |||||
n.AvgBlockTime = (1.0 / n.blockTimeMeter.Rate1()) * 1000 // 1/s to ms | |||||
} else { | |||||
n.AvgBlockTime = 0.0 | |||||
} | |||||
n.txThroughputMeter.Mark(int64(b.NumTxs)) | |||||
n.AvgTxThroughput = n.txThroughputMeter.Rate1() | |||||
} | |||||
func (n *Network) NewBlockLatency(l float64) { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
n.blockLatencyMeter.Mark(int64(l)) | |||||
n.AvgBlockLatency = n.blockLatencyMeter.Rate1() / 1000000.0 // ns to ms | |||||
} | |||||
// RecalculateUptime calculates uptime on demand. | |||||
func (n *Network) RecalculateUptime() { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
since := time.Since(n.UptimeData.StartTime) | |||||
uptime := since - n.UptimeData.totalDownTime | |||||
if n.Health != FullHealth { | |||||
uptime -= time.Since(n.UptimeData.wentDown) | |||||
} | |||||
n.UptimeData.Uptime = (float64(uptime) / float64(since)) * 100.0 | |||||
} | |||||
// NodeIsDown is called when the node disconnects for whatever reason. | |||||
// Must be safe to call multiple times. | |||||
func (n *Network) NodeIsDown(name string) { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
if online, ok := n.nodeStatusMap[name]; !ok || online { | |||||
n.nodeStatusMap[name] = false | |||||
n.NumNodesMonitoredOnline-- | |||||
n.UptimeData.wentDown = time.Now() | |||||
n.updateHealth() | |||||
} | |||||
} | |||||
// NodeIsOnline is called when connection to the node is restored. | |||||
// Must be safe to call multiple times. | |||||
func (n *Network) NodeIsOnline(name string) { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
if online, ok := n.nodeStatusMap[name]; ok && !online { | |||||
n.nodeStatusMap[name] = true | |||||
n.NumNodesMonitoredOnline++ | |||||
n.UptimeData.totalDownTime += time.Since(n.UptimeData.wentDown) | |||||
n.updateHealth() | |||||
} | |||||
} | |||||
// NewNode is called when the new node is added to the monitor. | |||||
func (n *Network) NewNode(name string) { | |||||
n.NumNodesMonitored++ | |||||
n.NumNodesMonitoredOnline++ | |||||
} | |||||
// NodeDeleted is called when the node is deleted from under the monitor. | |||||
func (n *Network) NodeDeleted(name string) { | |||||
n.NumNodesMonitored-- | |||||
n.NumNodesMonitoredOnline-- | |||||
} | |||||
func (n *Network) updateHealth() { | |||||
// if we are connected to all validators, we're at full health | |||||
// TODO: make sure they're all at the same height (within a block) | |||||
// and all proposing (and possibly validating ) Alternatively, just | |||||
// check there hasn't been a new round in numValidators rounds | |||||
if n.NumValidators != 0 && n.NumNodesMonitoredOnline == n.NumValidators { | |||||
n.Health = FullHealth | |||||
} else if n.NumNodesMonitoredOnline > 0 && n.NumNodesMonitoredOnline <= n.NumNodesMonitored { | |||||
n.Health = ModerateHealth | |||||
} else { | |||||
n.Health = Dead | |||||
} | |||||
} | |||||
func (n *Network) UpdateNumValidatorsForHeight(num int, height int64) { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
if n.Height <= height { | |||||
n.NumValidators = num | |||||
} | |||||
} | |||||
func (n *Network) GetHealthString() string { | |||||
switch n.Health { | |||||
case FullHealth: | |||||
return "full" | |||||
case ModerateHealth: | |||||
return "moderate" | |||||
case Dead: | |||||
return "dead" | |||||
default: | |||||
return "undefined" | |||||
} | |||||
} | |||||
// Uptime returns network's uptime in percentages. | |||||
func (n *Network) Uptime() float64 { | |||||
n.mu.Lock() | |||||
defer n.mu.Unlock() | |||||
return n.UptimeData.Uptime | |||||
} | |||||
// StartTime returns time we started monitoring. | |||||
func (n *Network) StartTime() time.Time { | |||||
return n.UptimeData.StartTime | |||||
} |
@ -0,0 +1,79 @@ | |||||
package monitor_test | |||||
import ( | |||||
"testing" | |||||
"time" | |||||
"github.com/stretchr/testify/assert" | |||||
tmtypes "github.com/tendermint/tendermint/types" | |||||
monitor "github.com/tendermint/tools/tm-monitor/monitor" | |||||
) | |||||
func TestNetworkNewBlock(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
n.NewBlock(tmtypes.Header{Height: 5, NumTxs: 100}) | |||||
assert.Equal(t, int64(5), n.Height) | |||||
assert.Equal(t, 0.0, n.AvgBlockTime) | |||||
assert.Equal(t, 0.0, n.AvgTxThroughput) | |||||
} | |||||
func TestNetworkNewBlockLatency(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
n.NewBlockLatency(9000000.0) // nanoseconds | |||||
assert.Equal(t, 0.0, n.AvgBlockLatency) | |||||
} | |||||
func TestNetworkNodeIsDownThenOnline(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
n.NewNode("test") | |||||
n.NodeIsDown("test") | |||||
assert.Equal(t, 0, n.NumNodesMonitoredOnline) | |||||
assert.Equal(t, monitor.Dead, n.Health) | |||||
n.NodeIsDown("test") | |||||
assert.Equal(t, 0, n.NumNodesMonitoredOnline) | |||||
n.NodeIsOnline("test") | |||||
assert.Equal(t, 1, n.NumNodesMonitoredOnline) | |||||
assert.Equal(t, monitor.ModerateHealth, n.Health) | |||||
n.NodeIsOnline("test") | |||||
assert.Equal(t, 1, n.NumNodesMonitoredOnline) | |||||
} | |||||
func TestNetworkNewNode(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
assert.Equal(t, 0, n.NumNodesMonitored) | |||||
assert.Equal(t, 0, n.NumNodesMonitoredOnline) | |||||
n.NewNode("test") | |||||
assert.Equal(t, 1, n.NumNodesMonitored) | |||||
assert.Equal(t, 1, n.NumNodesMonitoredOnline) | |||||
} | |||||
func TestNetworkNodeDeleted(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
n.NewNode("test") | |||||
n.NodeDeleted("test") | |||||
assert.Equal(t, 0, n.NumNodesMonitored) | |||||
assert.Equal(t, 0, n.NumNodesMonitoredOnline) | |||||
} | |||||
func TestNetworkGetHealthString(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
assert.Equal(t, "full", n.GetHealthString()) | |||||
n.Health = monitor.ModerateHealth | |||||
assert.Equal(t, "moderate", n.GetHealthString()) | |||||
n.Health = monitor.Dead | |||||
assert.Equal(t, "dead", n.GetHealthString()) | |||||
} | |||||
func TestNetworkUptime(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
assert.Equal(t, 100.0, n.Uptime()) | |||||
} | |||||
func TestNetworkStartTime(t *testing.T) { | |||||
n := monitor.NewNetwork() | |||||
assert.True(t, n.StartTime().Before(time.Now())) | |||||
} |
@ -0,0 +1,260 @@ | |||||
package monitor | |||||
import ( | |||||
"encoding/json" | |||||
"math" | |||||
"time" | |||||
"github.com/pkg/errors" | |||||
crypto "github.com/tendermint/go-crypto" | |||||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||||
rpc_client "github.com/tendermint/tendermint/rpc/lib/client" | |||||
tmtypes "github.com/tendermint/tendermint/types" | |||||
"github.com/tendermint/tmlibs/events" | |||||
"github.com/tendermint/tmlibs/log" | |||||
em "github.com/tendermint/tools/tm-monitor/eventmeter" | |||||
) | |||||
const maxRestarts = 25 | |||||
type Node struct { | |||||
rpcAddr string | |||||
IsValidator bool `json:"is_validator"` // validator or non-validator? | |||||
pubKey crypto.PubKey `json:"pub_key"` | |||||
Name string `json:"name"` | |||||
Online bool `json:"online"` | |||||
Height int64 `json:"height"` | |||||
BlockLatency float64 `json:"block_latency" amino:"unsafe"` // ms, interval between block commits | |||||
// em holds the ws connection. Each eventMeter callback is called in a separate go-routine. | |||||
em eventMeter | |||||
// rpcClient is an client for making RPC calls to TM | |||||
rpcClient rpc_client.HTTPClient | |||||
blockCh chan<- tmtypes.Header | |||||
blockLatencyCh chan<- float64 | |||||
disconnectCh chan<- bool | |||||
checkIsValidatorInterval time.Duration | |||||
quit chan struct{} | |||||
logger log.Logger | |||||
} | |||||
func NewNode(rpcAddr string, options ...func(*Node)) *Node { | |||||
em := em.NewEventMeter(rpcAddr, UnmarshalEvent) | |||||
rpcClient := rpc_client.NewURIClient(rpcAddr) // HTTP client by default | |||||
rpcClient.SetCodec(cdc) | |||||
return NewNodeWithEventMeterAndRpcClient(rpcAddr, em, rpcClient, options...) | |||||
} | |||||
func NewNodeWithEventMeterAndRpcClient(rpcAddr string, em eventMeter, rpcClient rpc_client.HTTPClient, options ...func(*Node)) *Node { | |||||
n := &Node{ | |||||
rpcAddr: rpcAddr, | |||||
em: em, | |||||
rpcClient: rpcClient, | |||||
Name: rpcAddr, | |||||
quit: make(chan struct{}), | |||||
checkIsValidatorInterval: 5 * time.Second, | |||||
logger: log.NewNopLogger(), | |||||
} | |||||
for _, option := range options { | |||||
option(n) | |||||
} | |||||
return n | |||||
} | |||||
// SetCheckIsValidatorInterval lets you change interval for checking whenever | |||||
// node is still a validator or not. | |||||
func SetCheckIsValidatorInterval(d time.Duration) func(n *Node) { | |||||
return func(n *Node) { | |||||
n.checkIsValidatorInterval = d | |||||
} | |||||
} | |||||
func (n *Node) SendBlocksTo(ch chan<- tmtypes.Header) { | |||||
n.blockCh = ch | |||||
} | |||||
func (n *Node) SendBlockLatenciesTo(ch chan<- float64) { | |||||
n.blockLatencyCh = ch | |||||
} | |||||
func (n *Node) NotifyAboutDisconnects(ch chan<- bool) { | |||||
n.disconnectCh = ch | |||||
} | |||||
// SetLogger lets you set your own logger | |||||
func (n *Node) SetLogger(l log.Logger) { | |||||
n.logger = l | |||||
n.em.SetLogger(l) | |||||
} | |||||
func (n *Node) Start() error { | |||||
if err := n.em.Start(); err != nil { | |||||
return err | |||||
} | |||||
n.em.RegisterLatencyCallback(latencyCallback(n)) | |||||
err := n.em.Subscribe(tmtypes.EventQueryNewBlockHeader.String(), newBlockCallback(n)) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
n.em.RegisterDisconnectCallback(disconnectCallback(n)) | |||||
n.Online = true | |||||
n.checkIsValidator() | |||||
go n.checkIsValidatorLoop() | |||||
return nil | |||||
} | |||||
func (n *Node) Stop() { | |||||
n.Online = false | |||||
n.em.Stop() | |||||
close(n.quit) | |||||
} | |||||
// implements eventmeter.EventCallbackFunc | |||||
func newBlockCallback(n *Node) em.EventCallbackFunc { | |||||
return func(metric *em.EventMetric, data interface{}) { | |||||
block := data.(tmtypes.TMEventData).(tmtypes.EventDataNewBlockHeader).Header | |||||
n.Height = block.Height | |||||
n.logger.Info("new block", "height", block.Height, "numTxs", block.NumTxs) | |||||
if n.blockCh != nil { | |||||
n.blockCh <- *block | |||||
} | |||||
} | |||||
} | |||||
// implements eventmeter.EventLatencyFunc | |||||
func latencyCallback(n *Node) em.LatencyCallbackFunc { | |||||
return func(latency float64) { | |||||
n.BlockLatency = latency / 1000000.0 // ns to ms | |||||
n.logger.Info("new block latency", "latency", n.BlockLatency) | |||||
if n.blockLatencyCh != nil { | |||||
n.blockLatencyCh <- latency | |||||
} | |||||
} | |||||
} | |||||
// implements eventmeter.DisconnectCallbackFunc | |||||
func disconnectCallback(n *Node) em.DisconnectCallbackFunc { | |||||
return func() { | |||||
n.Online = false | |||||
n.logger.Info("status", "down") | |||||
if n.disconnectCh != nil { | |||||
n.disconnectCh <- true | |||||
} | |||||
} | |||||
} | |||||
func (n *Node) RestartEventMeterBackoff() error { | |||||
attempt := 0 | |||||
for { | |||||
d := time.Duration(math.Exp2(float64(attempt))) | |||||
time.Sleep(d * time.Second) | |||||
if err := n.em.Start(); err != nil { | |||||
n.logger.Info("restart failed", "err", err) | |||||
} else { | |||||
// TODO: authenticate pubkey | |||||
return nil | |||||
} | |||||
attempt++ | |||||
if attempt > maxRestarts { | |||||
return errors.New("Reached max restarts") | |||||
} | |||||
} | |||||
} | |||||
func (n *Node) NumValidators() (height int64, num int, err error) { | |||||
height, vals, err := n.validators() | |||||
if err != nil { | |||||
return 0, 0, err | |||||
} | |||||
return height, len(vals), nil | |||||
} | |||||
func (n *Node) validators() (height int64, validators []*tmtypes.Validator, err error) { | |||||
vals := new(ctypes.ResultValidators) | |||||
if _, err = n.rpcClient.Call("validators", nil, vals); err != nil { | |||||
return 0, make([]*tmtypes.Validator, 0), err | |||||
} | |||||
return vals.BlockHeight, vals.Validators, nil | |||||
} | |||||
func (n *Node) checkIsValidatorLoop() { | |||||
for { | |||||
select { | |||||
case <-n.quit: | |||||
return | |||||
case <-time.After(n.checkIsValidatorInterval): | |||||
n.checkIsValidator() | |||||
} | |||||
} | |||||
} | |||||
func (n *Node) checkIsValidator() { | |||||
_, validators, err := n.validators() | |||||
if err == nil { | |||||
for _, v := range validators { | |||||
key, err1 := n.getPubKey() | |||||
// TODO: use bytes.Equal | |||||
if err1 == nil && v.PubKey == key { | |||||
n.IsValidator = true | |||||
} | |||||
} | |||||
} else { | |||||
n.logger.Info("check is validator failed", "err", err) | |||||
} | |||||
} | |||||
func (n *Node) getPubKey() (crypto.PubKey, error) { | |||||
if n.pubKey != nil { | |||||
return n.pubKey, nil | |||||
} | |||||
status := new(ctypes.ResultStatus) | |||||
_, err := n.rpcClient.Call("status", nil, status) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
n.pubKey = status.ValidatorInfo.PubKey | |||||
return n.pubKey, nil | |||||
} | |||||
type eventMeter interface { | |||||
Start() error | |||||
Stop() | |||||
RegisterLatencyCallback(em.LatencyCallbackFunc) | |||||
RegisterDisconnectCallback(em.DisconnectCallbackFunc) | |||||
Subscribe(string, em.EventCallbackFunc) error | |||||
Unsubscribe(string) error | |||||
SetLogger(l log.Logger) | |||||
} | |||||
// UnmarshalEvent unmarshals a json event | |||||
func UnmarshalEvent(b json.RawMessage) (string, events.EventData, error) { | |||||
event := new(ctypes.ResultEvent) | |||||
if err := cdc.UnmarshalJSON(b, event); err != nil { | |||||
return "", nil, err | |||||
} | |||||
return event.Query, event.Data, nil | |||||
} |