diff --git a/.gitignore b/.gitignore index c013b0e86..929e38897 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,10 @@ scripts/cutWALUntil/cutWALUntil libs/pubsub/query/fuzz_test/output shunit2 + +*/vendor +*/.glide +.terraform +terraform.tfstate +terraform.tfstate.backup +terraform.tfstate.d diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..f9d8128ea --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +* @melekes @Greg-Szabo +*.md @zramsay +*.rst @zramsay diff --git a/README.md b/README.md new file mode 100644 index 000000000..aeb411410 --- /dev/null +++ b/README.md @@ -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. diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 000000000..9974388f1 --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,4 @@ +BUILD +RPMS +SPECS +tmp diff --git a/build/LICENSE b/build/LICENSE new file mode 100644 index 000000000..bb66bb350 --- /dev/null +++ b/build/LICENSE @@ -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. diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 000000000..a47644b63 --- /dev/null +++ b/build/Makefile @@ -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 + diff --git a/build/RPM-GPG-KEY-Tendermint b/build/RPM-GPG-KEY-Tendermint new file mode 100644 index 000000000..e6f200d87 --- /dev/null +++ b/build/RPM-GPG-KEY-Tendermint @@ -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----- diff --git a/build/Release b/build/Release new file mode 100644 index 000000000..9003d1320 --- /dev/null +++ b/build/Release @@ -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 diff --git a/build/Release_amd64 b/build/Release_amd64 new file mode 100644 index 000000000..1f2ecbfe2 --- /dev/null +++ b/build/Release_amd64 @@ -0,0 +1,5 @@ +Archive: stable +Component: main +Origin: Tendermint +Label: Tendermint +Architecture: amd64 diff --git a/build/_gpg b/build/_gpg new file mode 100644 index 000000000..73742b5d8 --- /dev/null +++ b/build/_gpg @@ -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 diff --git a/build/basecoind/DEBIAN/changelog b/build/basecoind/DEBIAN/changelog new file mode 100644 index 000000000..260718eaf --- /dev/null +++ b/build/basecoind/DEBIAN/changelog @@ -0,0 +1,6 @@ +basecoind (@VERSION@) @STABILITY@; urgency=medium + + * Automatic build. See https://github.com/cosmos/cosmos-sdk for more information. + + -- Greg Szabo @DATETIMESTAMP@ + diff --git a/build/basecoind/DEBIAN/compat b/build/basecoind/DEBIAN/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/build/basecoind/DEBIAN/compat @@ -0,0 +1 @@ +9 diff --git a/build/basecoind/DEBIAN/control b/build/basecoind/DEBIAN/control new file mode 100644 index 000000000..c15d49110 --- /dev/null +++ b/build/basecoind/DEBIAN/control @@ -0,0 +1,14 @@ +Source: basecoind +Section: net +Priority: optional +Maintainer: Greg Szabo +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. + diff --git a/build/basecoind/DEBIAN/copyright b/build/basecoind/DEBIAN/copyright new file mode 100644 index 000000000..fe449650c --- /dev/null +++ b/build/basecoind/DEBIAN/copyright @@ -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'. diff --git a/build/basecoind/DEBIAN/postinst b/build/basecoind/DEBIAN/postinst new file mode 100644 index 000000000..d7d8f4413 --- /dev/null +++ b/build/basecoind/DEBIAN/postinst @@ -0,0 +1,41 @@ +#!/bin/sh +# postinst script for basecoind +# + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/basecoind/DEBIAN/postrm b/build/basecoind/DEBIAN/postrm new file mode 100644 index 000000000..b84c9f2a4 --- /dev/null +++ b/build/basecoind/DEBIAN/postrm @@ -0,0 +1,41 @@ +#!/bin/sh +# postrm script for basecoin +# + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# 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 diff --git a/build/basecoind/DEBIAN/preinst b/build/basecoind/DEBIAN/preinst new file mode 100644 index 000000000..53124c0ce --- /dev/null +++ b/build/basecoind/DEBIAN/preinst @@ -0,0 +1,38 @@ +#!/bin/sh +# preinst script for basecoind +# + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# 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 diff --git a/build/basecoind/DEBIAN/prerm b/build/basecoind/DEBIAN/prerm new file mode 100644 index 000000000..18ef42079 --- /dev/null +++ b/build/basecoind/DEBIAN/prerm @@ -0,0 +1,38 @@ +#!/bin/sh +# prerm script for basecoin +# + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/basecoind/etc/systemd/system-preset/50-basecoind.preset b/build/basecoind/etc/systemd/system-preset/50-basecoind.preset new file mode 100644 index 000000000..358334fc3 --- /dev/null +++ b/build/basecoind/etc/systemd/system-preset/50-basecoind.preset @@ -0,0 +1,2 @@ +disable basecoind.service + diff --git a/build/basecoind/etc/systemd/system/basecoind.service b/build/basecoind/etc/systemd/system/basecoind.service new file mode 100644 index 000000000..68b46d84f --- /dev/null +++ b/build/basecoind/etc/systemd/system/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 + diff --git a/build/basecoind/usr/share/basecoind/key.json b/build/basecoind/usr/share/basecoind/key.json new file mode 100644 index 000000000..bdefe8fd4 --- /dev/null +++ b/build/basecoind/usr/share/basecoind/key.json @@ -0,0 +1,12 @@ +{ + "address": "1B1BE55F969F54064628A63B9559E7C21C925165", + "priv_key": { + "type": "ed25519", + "data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + }, + "pub_key": { + "type": "ed25519", + "data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + } +} + diff --git a/build/basecoind/usr/share/basecoind/key2.json b/build/basecoind/usr/share/basecoind/key2.json new file mode 100644 index 000000000..ddfc6809b --- /dev/null +++ b/build/basecoind/usr/share/basecoind/key2.json @@ -0,0 +1,12 @@ +{ + "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", + "priv_key": { + "type": "ed25519", + "data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + }, + "pub_key": { + "type": "ed25519", + "data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + } +} + diff --git a/build/ethermint/DEBIAN/changelog b/build/ethermint/DEBIAN/changelog new file mode 100644 index 000000000..76a1fb154 --- /dev/null +++ b/build/ethermint/DEBIAN/changelog @@ -0,0 +1,6 @@ +ethermint (@VERSION@) @STABILITY@; urgency=medium + + * Automatic build. See https://github.com/tendermint/tendermint for more information. + + -- Greg Szabo @DATETIMESTAMP@ + diff --git a/build/ethermint/DEBIAN/compat b/build/ethermint/DEBIAN/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/build/ethermint/DEBIAN/compat @@ -0,0 +1 @@ +9 diff --git a/build/ethermint/DEBIAN/control b/build/ethermint/DEBIAN/control new file mode 100644 index 000000000..2d8b3b002 --- /dev/null +++ b/build/ethermint/DEBIAN/control @@ -0,0 +1,15 @@ +Source: ethermint +Section: net +Priority: optional +Maintainer: Greg Szabo +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. + diff --git a/build/ethermint/DEBIAN/copyright b/build/ethermint/DEBIAN/copyright new file mode 100644 index 000000000..6d1bab01b --- /dev/null +++ b/build/ethermint/DEBIAN/copyright @@ -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'. diff --git a/build/ethermint/DEBIAN/postinst b/build/ethermint/DEBIAN/postinst new file mode 100644 index 000000000..439fdc395 --- /dev/null +++ b/build/ethermint/DEBIAN/postinst @@ -0,0 +1,46 @@ +#!/bin/sh +# postinst script for ethermint +# + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/ethermint/DEBIAN/postrm b/build/ethermint/DEBIAN/postrm new file mode 100644 index 000000000..f1d9d6afc --- /dev/null +++ b/build/ethermint/DEBIAN/postrm @@ -0,0 +1,41 @@ +#!/bin/sh +# postrm script for ethermint +# + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# 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 diff --git a/build/ethermint/DEBIAN/preinst b/build/ethermint/DEBIAN/preinst new file mode 100644 index 000000000..829112e6b --- /dev/null +++ b/build/ethermint/DEBIAN/preinst @@ -0,0 +1,38 @@ +#!/bin/sh +# preinst script for ethermint +# + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# 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 diff --git a/build/ethermint/DEBIAN/prerm b/build/ethermint/DEBIAN/prerm new file mode 100644 index 000000000..00a775cef --- /dev/null +++ b/build/ethermint/DEBIAN/prerm @@ -0,0 +1,38 @@ +#!/bin/sh +# prerm script for ethermint +# + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/ethermint/etc/systemd/system-preset/50-ethermint.preset b/build/ethermint/etc/systemd/system-preset/50-ethermint.preset new file mode 100644 index 000000000..836a28c30 --- /dev/null +++ b/build/ethermint/etc/systemd/system-preset/50-ethermint.preset @@ -0,0 +1,2 @@ +disable ethermint.service + diff --git a/build/ethermint/etc/systemd/system/ethermint.service b/build/ethermint/etc/systemd/system/ethermint.service new file mode 100644 index 000000000..f71a074ea --- /dev/null +++ b/build/ethermint/etc/systemd/system/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 + diff --git a/build/gaia/DEBIAN/changelog b/build/gaia/DEBIAN/changelog new file mode 100644 index 000000000..eca5fbc3d --- /dev/null +++ b/build/gaia/DEBIAN/changelog @@ -0,0 +1,6 @@ +gaia (@VERSION@) @STABILITY@; urgency=medium + + * Automatic build. See https://github.com/tendermint/basecoin for more information. + + -- Greg Szabo @DATETIMESTAMP@ + diff --git a/build/gaia/DEBIAN/compat b/build/gaia/DEBIAN/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/build/gaia/DEBIAN/compat @@ -0,0 +1 @@ +9 diff --git a/build/gaia/DEBIAN/control b/build/gaia/DEBIAN/control new file mode 100644 index 000000000..55d1cd5dd --- /dev/null +++ b/build/gaia/DEBIAN/control @@ -0,0 +1,14 @@ +Source: gaia +Section: net +Priority: optional +Maintainer: Greg Szabo +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. + diff --git a/build/gaia/DEBIAN/copyright b/build/gaia/DEBIAN/copyright new file mode 100644 index 000000000..ffc230134 --- /dev/null +++ b/build/gaia/DEBIAN/copyright @@ -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'. diff --git a/build/gaia/DEBIAN/postinst b/build/gaia/DEBIAN/postinst new file mode 100644 index 000000000..427b7c493 --- /dev/null +++ b/build/gaia/DEBIAN/postinst @@ -0,0 +1,41 @@ +#!/bin/sh +# postinst script for gaia +# + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/gaia/DEBIAN/postrm b/build/gaia/DEBIAN/postrm new file mode 100644 index 000000000..da526ec30 --- /dev/null +++ b/build/gaia/DEBIAN/postrm @@ -0,0 +1,41 @@ +#!/bin/sh +# postrm script for gaia +# + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# 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 diff --git a/build/gaia/DEBIAN/preinst b/build/gaia/DEBIAN/preinst new file mode 100644 index 000000000..382fa419f --- /dev/null +++ b/build/gaia/DEBIAN/preinst @@ -0,0 +1,38 @@ +#!/bin/sh +# preinst script for gaia +# + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# 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 diff --git a/build/gaia/DEBIAN/prerm b/build/gaia/DEBIAN/prerm new file mode 100644 index 000000000..165c1ab6a --- /dev/null +++ b/build/gaia/DEBIAN/prerm @@ -0,0 +1,38 @@ +#!/bin/sh +# prerm script for gaia +# + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/gaia/etc/systemd/system-preset/50-gaia.preset b/build/gaia/etc/systemd/system-preset/50-gaia.preset new file mode 100644 index 000000000..dfbf0bc06 --- /dev/null +++ b/build/gaia/etc/systemd/system-preset/50-gaia.preset @@ -0,0 +1,2 @@ +disable gaia.service + diff --git a/build/gaia/etc/systemd/system/gaia.service b/build/gaia/etc/systemd/system/gaia.service new file mode 100644 index 000000000..372fe9343 --- /dev/null +++ b/build/gaia/etc/systemd/system/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 + diff --git a/build/gaia/usr/share/gaia/key.json b/build/gaia/usr/share/gaia/key.json new file mode 100644 index 000000000..bdefe8fd4 --- /dev/null +++ b/build/gaia/usr/share/gaia/key.json @@ -0,0 +1,12 @@ +{ + "address": "1B1BE55F969F54064628A63B9559E7C21C925165", + "priv_key": { + "type": "ed25519", + "data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + }, + "pub_key": { + "type": "ed25519", + "data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + } +} + diff --git a/build/gaia/usr/share/gaia/key2.json b/build/gaia/usr/share/gaia/key2.json new file mode 100644 index 000000000..ddfc6809b --- /dev/null +++ b/build/gaia/usr/share/gaia/key2.json @@ -0,0 +1,12 @@ +{ + "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", + "priv_key": { + "type": "ed25519", + "data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + }, + "pub_key": { + "type": "ed25519", + "data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + } +} + diff --git a/build/generate-spec b/build/generate-spec new file mode 100755 index 000000000..4ca60a1d4 --- /dev/null +++ b/build/generate-spec @@ -0,0 +1,36 @@ +#!/bin/bash + +if [ $# -ne 3 ]; then + echo "Usage: $0 " + 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 + diff --git a/build/sign b/build/sign new file mode 100755 index 000000000..0371b5d4b --- /dev/null +++ b/build/sign @@ -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 \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 + diff --git a/build/spectemplates/app-template.spec b/build/spectemplates/app-template.spec new file mode 100644 index 000000000..6cb8145bb --- /dev/null +++ b/build/spectemplates/app-template.spec @@ -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 + diff --git a/build/spectemplates/basecoind.data b/build/spectemplates/basecoind.data new file mode 100644 index 000000000..36b172ecf --- /dev/null +++ b/build/spectemplates/basecoind.data @@ -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." + diff --git a/build/spectemplates/ethermint.data b/build/spectemplates/ethermint.data new file mode 100644 index 000000000..e9d403db7 --- /dev/null +++ b/build/spectemplates/ethermint.data @@ -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." + diff --git a/build/spectemplates/ethermint.spec b/build/spectemplates/ethermint.spec new file mode 100644 index 000000000..fc443e35b --- /dev/null +++ b/build/spectemplates/ethermint.spec @@ -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 + diff --git a/build/spectemplates/gaia.data b/build/spectemplates/gaia.data new file mode 100644 index 000000000..7152b1b51 --- /dev/null +++ b/build/spectemplates/gaia.data @@ -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." + diff --git a/build/spectemplates/tendermint.spec b/build/spectemplates/tendermint.spec new file mode 100644 index 000000000..68902a170 --- /dev/null +++ b/build/spectemplates/tendermint.spec @@ -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 + diff --git a/build/tendermint.list b/build/tendermint.list new file mode 100644 index 000000000..bba521af5 --- /dev/null +++ b/build/tendermint.list @@ -0,0 +1 @@ +deb http://tendermint-packages.s3-website-us-west-1.amazonaws.com/debian stable main diff --git a/build/tendermint.repo b/build/tendermint.repo new file mode 100644 index 000000000..439f98ecb --- /dev/null +++ b/build/tendermint.repo @@ -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 + diff --git a/build/tendermint/DEBIAN/changelog b/build/tendermint/DEBIAN/changelog new file mode 100644 index 000000000..4b016f845 --- /dev/null +++ b/build/tendermint/DEBIAN/changelog @@ -0,0 +1,6 @@ +tendermint (@VERSION@) @STABILITY@; urgency=medium + + * Automatic build. See https://github.com/tendermint/tendermint for more information. + + -- Greg Szabo @DATETIMESTAMP@ + diff --git a/build/tendermint/DEBIAN/compat b/build/tendermint/DEBIAN/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/build/tendermint/DEBIAN/compat @@ -0,0 +1 @@ +9 diff --git a/build/tendermint/DEBIAN/control b/build/tendermint/DEBIAN/control new file mode 100644 index 000000000..d9da17dd1 --- /dev/null +++ b/build/tendermint/DEBIAN/control @@ -0,0 +1,14 @@ +Source: tendermint +Section: net +Priority: optional +Maintainer: Greg Szabo +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. + diff --git a/build/tendermint/DEBIAN/copyright b/build/tendermint/DEBIAN/copyright new file mode 100644 index 000000000..15ee960dd --- /dev/null +++ b/build/tendermint/DEBIAN/copyright @@ -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'. diff --git a/mintnet-kubernetes/LICENSE b/mintnet-kubernetes/LICENSE new file mode 100644 index 000000000..64a33ddf1 --- /dev/null +++ b/mintnet-kubernetes/LICENSE @@ -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. diff --git a/mintnet-kubernetes/README.rst b/mintnet-kubernetes/README.rst new file mode 100644 index 000000000..9cfdbb8eb --- /dev/null +++ b/mintnet-kubernetes/README.rst @@ -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 `__ + +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 `__ +- on GCE with a single click in the web UI +- on AWS using `Kubernetes + Operations `__ +- on Linux machines (Digital Ocean) using + `kubeadm `__ +- on AWS, Azure, GCE or bare metal using `Kargo + (Ansible) `__ + +Please refer to `the official +documentation `__ +for overview and comparison of different options. + +Kubernetes on Digital Ocean +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Available options: + +- `kubeadm (alpha) `__ +- `kargo `__ +- `rancher `__ +- `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 `__ 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 `__ for Kubernetes on Google Compute +Engine. + +**Create a cluster** + +The recommended way is to use `Google 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 `__. + +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 `__ + +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.: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 `__) +- 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 `__, each zone +running an `API +server `__ 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 `__. +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. diff --git a/mintnet-kubernetes/app.template.yaml b/mintnet-kubernetes/app.template.yaml new file mode 100644 index 000000000..826b2e97f --- /dev/null +++ b/mintnet-kubernetes/app.template.yaml @@ -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 diff --git a/mintnet-kubernetes/assets/gce1.png b/mintnet-kubernetes/assets/gce1.png new file mode 100644 index 000000000..3bf3ad005 Binary files /dev/null and b/mintnet-kubernetes/assets/gce1.png differ diff --git a/mintnet-kubernetes/assets/gce2.png b/mintnet-kubernetes/assets/gce2.png new file mode 100644 index 000000000..358dcc04b Binary files /dev/null and b/mintnet-kubernetes/assets/gce2.png differ diff --git a/mintnet-kubernetes/assets/statefulset.png b/mintnet-kubernetes/assets/statefulset.png new file mode 100644 index 000000000..ac68d22b7 Binary files /dev/null and b/mintnet-kubernetes/assets/statefulset.png differ diff --git a/mintnet-kubernetes/assets/t_plus_k.png b/mintnet-kubernetes/assets/t_plus_k.png new file mode 100644 index 000000000..bee9fe56e Binary files /dev/null and b/mintnet-kubernetes/assets/t_plus_k.png differ diff --git a/mintnet-kubernetes/examples/basecoin/Makefile b/mintnet-kubernetes/examples/basecoin/Makefile new file mode 100644 index 000000000..6d54d57d6 --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/Makefile @@ -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 diff --git a/mintnet-kubernetes/examples/basecoin/README.md b/mintnet-kubernetes/examples/basecoin/README.md new file mode 100644 index 000000000..46911a096 --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/README.md @@ -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 +``` diff --git a/mintnet-kubernetes/examples/basecoin/app.yaml b/mintnet-kubernetes/examples/basecoin/app.yaml new file mode 100644 index 000000000..6206b1cdb --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/app.yaml @@ -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 diff --git a/mintnet-kubernetes/examples/basecoin/lightclient.md b/mintnet-kubernetes/examples/basecoin/lightclient.md new file mode 100644 index 000000000..11d07af1f --- /dev/null +++ b/mintnet-kubernetes/examples/basecoin/lightclient.md @@ -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 --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 + diff --git a/mintnet-kubernetes/examples/counter/Makefile b/mintnet-kubernetes/examples/counter/Makefile new file mode 100644 index 000000000..6d54d57d6 --- /dev/null +++ b/mintnet-kubernetes/examples/counter/Makefile @@ -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 diff --git a/mintnet-kubernetes/examples/counter/app.yaml b/mintnet-kubernetes/examples/counter/app.yaml new file mode 100644 index 000000000..fed35f102 --- /dev/null +++ b/mintnet-kubernetes/examples/counter/app.yaml @@ -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 diff --git a/mintnet-kubernetes/examples/dummy/Makefile b/mintnet-kubernetes/examples/dummy/Makefile new file mode 100644 index 000000000..825487fcd --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/Makefile @@ -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 diff --git a/mintnet-kubernetes/examples/dummy/app.yaml b/mintnet-kubernetes/examples/dummy/app.yaml new file mode 100644 index 000000000..5413bd501 --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/app.yaml @@ -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 diff --git a/mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml b/mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml new file mode 100644 index 000000000..fb0bf7236 --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/tm-monitor-pod.yaml @@ -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 diff --git a/mintnet-kubernetes/examples/dummy/transacter-pod.yaml b/mintnet-kubernetes/examples/dummy/transacter-pod.yaml new file mode 100644 index 000000000..6598e2a8b --- /dev/null +++ b/mintnet-kubernetes/examples/dummy/transacter-pod.yaml @@ -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 diff --git a/tm-bench/Dockerfile b/tm-bench/Dockerfile new file mode 100644 index 000000000..9adb2936e --- /dev/null +++ b/tm-bench/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:3.7 + +WORKDIR /app +COPY tm-bench /app/tm-bench + +ENTRYPOINT ["./tm-bench"] diff --git a/tm-bench/Dockerfile.dev b/tm-bench/Dockerfile.dev new file mode 100644 index 000000000..c950dda53 --- /dev/null +++ b/tm-bench/Dockerfile.dev @@ -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 diff --git a/tm-bench/Gopkg.lock b/tm-bench/Gopkg.lock new file mode 100644 index 000000000..0e5cd2021 --- /dev/null +++ b/tm-bench/Gopkg.lock @@ -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 diff --git a/tm-bench/Gopkg.toml b/tm-bench/Gopkg.toml new file mode 100644 index 000000000..4d1d0df77 --- /dev/null +++ b/tm-bench/Gopkg.toml @@ -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 diff --git a/tm-bench/LICENSE b/tm-bench/LICENSE new file mode 100644 index 000000000..f48913967 --- /dev/null +++ b/tm-bench/LICENSE @@ -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. diff --git a/tm-bench/Makefile b/tm-bench/Makefile new file mode 100644 index 000000000..b95aeee93 --- /dev/null +++ b/tm-bench/Makefile @@ -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 diff --git a/tm-bench/README.md b/tm-bench/README.md new file mode 100644 index 000000000..811141629 --- /dev/null +++ b/tm-bench/README.md @@ -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 diff --git a/tm-bench/bench_test.go b/tm-bench/bench_test.go new file mode 100644 index 000000000..9eaf0f7ea --- /dev/null +++ b/tm-bench/bench_test.go @@ -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 + } + } + } +} diff --git a/tm-bench/main.go b/tm-bench/main.go new file mode 100644 index 000000000..87a176f95 --- /dev/null +++ b/tm-bench/main.go @@ -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 [-broadcast-tx-method ]] + +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() + } +} diff --git a/tm-bench/transacter.go b/tm-bench/transacter.go new file mode 100644 index 000000000..d3a43774d --- /dev/null +++ b/tm-bench/transacter.go @@ -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 +} diff --git a/tm-monitor/Dockerfile b/tm-monitor/Dockerfile new file mode 100644 index 000000000..7edfaca66 --- /dev/null +++ b/tm-monitor/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:3.6 + +WORKDIR /app +COPY tm-monitor /app/tm-monitor + +ENTRYPOINT ["./tm-monitor"] diff --git a/tm-monitor/Dockerfile.dev b/tm-monitor/Dockerfile.dev new file mode 100644 index 000000000..5bfbbfd5a --- /dev/null +++ b/tm-monitor/Dockerfile.dev @@ -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 diff --git a/tm-monitor/Gopkg.lock b/tm-monitor/Gopkg.lock new file mode 100644 index 000000000..717312a27 --- /dev/null +++ b/tm-monitor/Gopkg.lock @@ -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 diff --git a/tm-monitor/Gopkg.toml b/tm-monitor/Gopkg.toml new file mode 100644 index 000000000..d5287a54c --- /dev/null +++ b/tm-monitor/Gopkg.toml @@ -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 diff --git a/tm-monitor/LICENSE b/tm-monitor/LICENSE new file mode 100644 index 000000000..20728d318 --- /dev/null +++ b/tm-monitor/LICENSE @@ -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. diff --git a/tm-monitor/Makefile b/tm-monitor/Makefile new file mode 100644 index 000000000..3371a0c19 --- /dev/null +++ b/tm-monitor/Makefile @@ -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 diff --git a/tm-monitor/README.md b/tm-monitor/README.md new file mode 100644 index 000000000..4c49775e3 --- /dev/null +++ b/tm-monitor/README.md @@ -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 diff --git a/tm-monitor/eventmeter/eventmeter.go b/tm-monitor/eventmeter/eventmeter.go new file mode 100644 index 000000000..731b38123 --- /dev/null +++ b/tm-monitor/eventmeter/eventmeter.go @@ -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() +} diff --git a/tm-monitor/main.go b/tm-monitor/main.go new file mode 100644 index 000000000..e5020eb3c --- /dev/null +++ b/tm-monitor/main.go @@ -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 +} diff --git a/tm-monitor/mock/eventmeter.go b/tm-monitor/mock/eventmeter.go new file mode 100644 index 000000000..6e83cd9d9 --- /dev/null +++ b/tm-monitor/mock/eventmeter.go @@ -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 +} \ No newline at end of file diff --git a/tm-monitor/monitor/monitor.go b/tm-monitor/monitor/monitor.go new file mode 100644 index 000000000..e53b33254 --- /dev/null +++ b/tm-monitor/monitor/monitor.go @@ -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) + } + } +} diff --git a/tm-monitor/monitor/monitor_test.go b/tm-monitor/monitor/monitor_test.go new file mode 100644 index 000000000..eb37800a7 --- /dev/null +++ b/tm-monitor/monitor/monitor_test.go @@ -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 +} diff --git a/tm-monitor/monitor/network.go b/tm-monitor/monitor/network.go new file mode 100644 index 000000000..9b147c06b --- /dev/null +++ b/tm-monitor/monitor/network.go @@ -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 +} diff --git a/tm-monitor/monitor/network_test.go b/tm-monitor/monitor/network_test.go new file mode 100644 index 000000000..d01afaf0d --- /dev/null +++ b/tm-monitor/monitor/network_test.go @@ -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())) +} diff --git a/tm-monitor/monitor/node.go b/tm-monitor/monitor/node.go new file mode 100644 index 000000000..7a597bef5 --- /dev/null +++ b/tm-monitor/monitor/node.go @@ -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 +} diff --git a/tm-monitor/monitor/node_test.go b/tm-monitor/monitor/node_test.go new file mode 100644 index 000000000..04732a1c8 --- /dev/null +++ b/tm-monitor/monitor/node_test.go @@ -0,0 +1,93 @@ +package monitor_test + +import ( + "testing" + + "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" + em "github.com/tendermint/tools/tm-monitor/eventmeter" + mock "github.com/tendermint/tools/tm-monitor/mock" + monitor "github.com/tendermint/tools/tm-monitor/monitor" + "github.com/tendermint/go-amino" +) + +const ( + blockHeight = int64(1) +) + +func TestNodeStartStop(t *testing.T) { + n, _ := startValidatorNode(t) + defer n.Stop() + + assert.Equal(t, true, n.Online) + assert.Equal(t, true, n.IsValidator) +} + +func TestNodeNewBlockReceived(t *testing.T) { + blockCh := make(chan tmtypes.Header, 100) + n, emMock := startValidatorNode(t) + defer n.Stop() + n.SendBlocksTo(blockCh) + + blockHeader := &tmtypes.Header{Height: 5} + emMock.Call("eventCallback", &em.EventMetric{}, tmtypes.EventDataNewBlockHeader{blockHeader}) + + assert.Equal(t, int64(5), n.Height) + assert.Equal(t, *blockHeader, <-blockCh) +} + +func TestNodeNewBlockLatencyReceived(t *testing.T) { + blockLatencyCh := make(chan float64, 100) + n, emMock := startValidatorNode(t) + defer n.Stop() + n.SendBlockLatenciesTo(blockLatencyCh) + + emMock.Call("latencyCallback", 1000000.0) + + assert.Equal(t, 1.0, n.BlockLatency) + assert.Equal(t, 1000000.0, <-blockLatencyCh) +} + +func TestNodeConnectionLost(t *testing.T) { + disconnectCh := make(chan bool, 100) + n, emMock := startValidatorNode(t) + defer n.Stop() + n.NotifyAboutDisconnects(disconnectCh) + + emMock.Call("disconnectCallback") + + assert.Equal(t, true, <-disconnectCh) + assert.Equal(t, false, n.Online) +} + +func TestNumValidators(t *testing.T) { + n, _ := startValidatorNode(t) + defer n.Stop() + + height, num, err := n.NumValidators() + assert.Nil(t, err) + assert.Equal(t, blockHeight, height) + assert.Equal(t, 1, num) +} + +func startValidatorNode(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) + + err := n.Start() + require.Nil(t, err) + return +} diff --git a/tm-monitor/monitor/wire.go b/tm-monitor/monitor/wire.go new file mode 100644 index 000000000..696b02778 --- /dev/null +++ b/tm-monitor/monitor/wire.go @@ -0,0 +1,12 @@ +package monitor + +import ( + amino "github.com/tendermint/go-amino" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +var cdc = amino.NewCodec() + +func init() { + ctypes.RegisterAmino(cdc) +} diff --git a/tm-monitor/rpc.go b/tm-monitor/rpc.go new file mode 100644 index 000000000..178eb7ea1 --- /dev/null +++ b/tm-monitor/rpc.go @@ -0,0 +1,124 @@ +package main + +import ( + "errors" + "net/http" + + rpc "github.com/tendermint/tendermint/rpc/lib/server" + "github.com/tendermint/tmlibs/log" + monitor "github.com/tendermint/tools/tm-monitor/monitor" +) + +func startRPC(listenAddr string, m *monitor.Monitor, logger log.Logger) { + routes := routes(m) + + mux := http.NewServeMux() + wm := rpc.NewWebsocketManager(routes, nil) + mux.HandleFunc("/websocket", wm.WebsocketHandler) + rpc.RegisterRPCFuncs(mux, routes, cdc, logger) + if _, err := rpc.StartHTTPServer(listenAddr, mux, logger); err != nil { + panic(err) + } +} + +func routes(m *monitor.Monitor) map[string]*rpc.RPCFunc { + return map[string]*rpc.RPCFunc{ + "status": rpc.NewRPCFunc(RPCStatus(m), ""), + "status/network": rpc.NewRPCFunc(RPCNetworkStatus(m), ""), + "status/node": rpc.NewRPCFunc(RPCNodeStatus(m), "name"), + "monitor": rpc.NewRPCFunc(RPCMonitor(m), "endpoint"), + "unmonitor": rpc.NewRPCFunc(RPCUnmonitor(m), "endpoint"), + + // "start_meter": rpc.NewRPCFunc(network.StartMeter, "chainID,valID,event"), + // "stop_meter": rpc.NewRPCFunc(network.StopMeter, "chainID,valID,event"), + // "meter": rpc.NewRPCFunc(GetMeterResult(network), "chainID,valID,event"), + } +} + +// RPCStatus returns common statistics for the network and statistics per node. +func RPCStatus(m *monitor.Monitor) interface{} { + return func() (networkAndNodes, error) { + return networkAndNodes{m.Network, m.Nodes}, nil + } +} + +// RPCNetworkStatus returns common statistics for the network. +func RPCNetworkStatus(m *monitor.Monitor) interface{} { + return func() (*monitor.Network, error) { + return m.Network, nil + } +} + +// RPCNodeStatus returns statistics for the given node. +func RPCNodeStatus(m *monitor.Monitor) interface{} { + return func(name string) (*monitor.Node, error) { + if i, n := m.NodeByName(name); i != -1 { + return n, nil + } + return nil, errors.New("Cannot find node with that name") + } +} + +// RPCMonitor allows to dynamically add a endpoint to under the monitor. Safe +// to call multiple times. +func RPCMonitor(m *monitor.Monitor) interface{} { + return func(endpoint string) (*monitor.Node, error) { + i, n := m.NodeByName(endpoint) + if i == -1 { + n = monitor.NewNode(endpoint) + if err := m.Monitor(n); err != nil { + return nil, err + } + } + return n, nil + } +} + +// RPCUnmonitor removes the given endpoint from under the monitor. +func RPCUnmonitor(m *monitor.Monitor) interface{} { + return func(endpoint string) (bool, error) { + if i, n := m.NodeByName(endpoint); i != -1 { + m.Unmonitor(n) + return true, nil + } + return false, errors.New("Cannot find node with that name") + } +} + +// func (tn *TendermintNetwork) StartMeter(chainID, valID, eventID string) error { +// tn.mtx.Lock() +// defer tn.mtx.Unlock() +// val, err := tn.getChainVal(chainID, valID) +// if err != nil { +// return err +// } +// return val.EventMeter().Subscribe(eventID, nil) +// } + +// func (tn *TendermintNetwork) StopMeter(chainID, valID, eventID string) error { +// tn.mtx.Lock() +// defer tn.mtx.Unlock() +// val, err := tn.getChainVal(chainID, valID) +// if err != nil { +// return err +// } +// return val.EventMeter().Unsubscribe(eventID) +// } + +// func (tn *TendermintNetwork) GetMeter(chainID, valID, eventID string) (*eventmeter.EventMetric, error) { +// tn.mtx.Lock() +// defer tn.mtx.Unlock() +// val, err := tn.getChainVal(chainID, valID) +// if err != nil { +// return nil, err +// } + +// return val.EventMeter().GetMetric(eventID) +// } + +//--> types + +type networkAndNodes struct { + Network *monitor.Network `json:"network"` + Nodes []*monitor.Node `json:"nodes"` +} diff --git a/tm-monitor/ton.go b/tm-monitor/ton.go new file mode 100644 index 000000000..8052db2ec --- /dev/null +++ b/tm-monitor/ton.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "io" + "os" + "text/tabwriter" + "time" + + monitor "github.com/tendermint/tools/tm-monitor/monitor" +) + +const ( + // Default refresh rate - 200ms + defaultRefreshRate = time.Millisecond * 200 +) + +// Ton - table of nodes. +// +// It produces the unordered list of nodes and updates it periodically. +// +// Default output is stdout, but it could be changed. Note if you want for +// refresh to work properly, output must support [ANSI escape +// codes](http://en.wikipedia.org/wiki/ANSI_escape_code). +// +// Ton was inspired by [Linux top +// program](https://en.wikipedia.org/wiki/Top_(software)) as the name suggests. +type Ton struct { + monitor *monitor.Monitor + + RefreshRate time.Duration + Output io.Writer + quit chan struct{} +} + +func NewTon(m *monitor.Monitor) *Ton { + return &Ton{ + RefreshRate: defaultRefreshRate, + Output: os.Stdout, + quit: make(chan struct{}), + monitor: m, + } +} + +func (o *Ton) Start() { + clearScreen(o.Output) + o.Print() + go o.refresher() +} + +func (o *Ton) Print() { + moveCursor(o.Output, 1, 1) + o.printHeader() + fmt.Println() + o.printTable() +} + +func (o *Ton) Stop() { + close(o.quit) +} + +func (o *Ton) printHeader() { + n := o.monitor.Network + fmt.Fprintf(o.Output, "%v up %.2f%%\n", n.StartTime(), n.Uptime()) + fmt.Println() + fmt.Fprintf(o.Output, "Height: %d\n", n.Height) + fmt.Fprintf(o.Output, "Avg block time: %.3f ms\n", n.AvgBlockTime) + fmt.Fprintf(o.Output, "Avg tx throughput: %.0f per sec\n", n.AvgTxThroughput) + fmt.Fprintf(o.Output, "Avg block latency: %.3f ms\n", n.AvgBlockLatency) + fmt.Fprintf(o.Output, "Active nodes: %d/%d (health: %s) Validators: %d\n", n.NumNodesMonitoredOnline, n.NumNodesMonitored, n.GetHealthString(), n.NumValidators) +} + +func (o *Ton) printTable() { + w := tabwriter.NewWriter(o.Output, 0, 0, 5, ' ', 0) + fmt.Fprintln(w, "NAME\tHEIGHT\tBLOCK LATENCY\tONLINE\tVALIDATOR\t") + for _, n := range o.monitor.Nodes { + fmt.Fprintln(w, fmt.Sprintf("%s\t%d\t%.3f ms\t%v\t%v\t", n.Name, n.Height, n.BlockLatency, n.Online, n.IsValidator)) + } + w.Flush() +} + +// Internal loop for refreshing +func (o *Ton) refresher() { + for { + select { + case <-o.quit: + return + case <-time.After(o.RefreshRate): + o.Print() + } + } +} + +func clearScreen(w io.Writer) { + fmt.Fprint(w, "\033[2J") +} + +func moveCursor(w io.Writer, x int, y int) { + fmt.Fprintf(w, "\033[%d;%dH", x, y) +} diff --git a/tm-monitor/wire.go b/tm-monitor/wire.go new file mode 100644 index 000000000..071c363b0 --- /dev/null +++ b/tm-monitor/wire.go @@ -0,0 +1,12 @@ +package main + +import ( + amino "github.com/tendermint/go-amino" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +var cdc = amino.NewCodec() + +func init() { + ctypes.RegisterAmino(cdc) +} diff --git a/transact/transact.go b/transact/transact.go new file mode 100644 index 000000000..51969f683 --- /dev/null +++ b/transact/transact.go @@ -0,0 +1,140 @@ +package main + +import ( + "crypto/rand" + "encoding/binary" + "encoding/hex" + "flag" + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/tendermint/go-rpc/client" + rpctypes "github.com/tendermint/go-rpc/types" +) + +func main() { + flag.Parse() + args := flag.Args() + if len(args) < 2 { + fmt.Println("transact.go expects at least two arguments (ntxs, hosts)") + os.Exit(1) + } + + nTxS, hostS := args[0], args[1] + nTxs, err := strconv.Atoi(nTxS) + if err != nil { + fmt.Println("ntxs must be an integer:", err) + os.Exit(1) + } + + hosts := strings.Split(hostS, ",") + + errCh := make(chan error, 1000) + + wg := new(sync.WaitGroup) + wg.Add(len(hosts)) + start := time.Now() + fmt.Printf("Sending %d txs on every host %v\n", nTxs, hosts) + for i, host := range hosts { + go broadcastTxsToHost(wg, errCh, i, host, nTxs, 0) + } + wg.Wait() + fmt.Println("Done broadcasting txs. Took", time.Since(start)) + +} + +func broadcastTxsToHost(wg *sync.WaitGroup, errCh chan error, valI int, valHost string, nTxs int, txCount int) { + reconnectSleepSeconds := time.Second * 1 + + // thisStart := time.Now() + // cli := rpcclient.NewClientURI(valHost + ":26657") + fmt.Println("Connecting to host to broadcast txs", valI, valHost) + cli := rpcclient.NewWSClient(valHost, "/websocket") + if _, err := cli.Start(); err != nil { + if nTxs == 0 { + time.Sleep(reconnectSleepSeconds) + broadcastTxsToHost(wg, errCh, valI, valHost, nTxs, txCount) + return + } + fmt.Printf("Error starting websocket connection to val%d (%s): %v\n", valI, valHost, err) + os.Exit(1) + } + + reconnect := make(chan struct{}) + go func(count int) { + LOOP: + for { + ticker := time.NewTicker(reconnectSleepSeconds) + select { + case <-cli.ResultsCh: + count += 1 + // nTxs == 0 means just loop forever + if nTxs > 0 && count == nTxs { + break LOOP + } + case err := <-cli.ErrorsCh: + fmt.Println("err: val", valI, valHost, err) + case <-cli.Quit: + broadcastTxsToHost(wg, errCh, valI, valHost, nTxs, count) + return + case <-reconnect: + broadcastTxsToHost(wg, errCh, valI, valHost, nTxs, count) + return + case <-ticker.C: + if nTxs == 0 { + cli.Stop() + broadcastTxsToHost(wg, errCh, valI, valHost, nTxs, count) + return + } + } + } + fmt.Printf("Received all responses from node %d (%s)\n", valI, valHost) + wg.Done() + }(txCount) + var i = 0 + for { + /* if i%(nTxs/4) == 0 { + fmt.Printf("Have sent %d txs to node %d. Total time so far: %v\n", i, valI, time.Since(thisStart)) + }*/ + + if !cli.IsRunning() { + return + } + + tx := generateTx(i, valI) + if err := cli.WriteJSON(rpctypes.RPCRequest{ + JSONRPC: "2.0", + ID: "", + Method: "broadcast_tx_async", + Params: []interface{}{hex.EncodeToString(tx)}, + }); err != nil { + fmt.Printf("Error sending tx %d to validator %d: %v. Attempt reconnect\n", i, valI, err) + reconnect <- struct{}{} + return + } + i += 1 + if nTxs > 0 && i >= nTxs { + break + } else if nTxs == 0 { + time.Sleep(time.Millisecond * 1) + } + } + fmt.Printf("Done sending %d txs to node s%d (%s)\n", nTxs, valI, valHost) +} + +func generateTx(i, valI int) []byte { + // a tx encodes the validator index, the tx number, and some random junk + // TODO: read random bytes into more of the tx + tx := make([]byte, 250) + binary.PutUvarint(tx[:32], uint64(valI)) + binary.PutUvarint(tx[32:64], uint64(i)) + if _, err := rand.Read(tx[234:]); err != nil { + fmt.Println("err reading from crypto/rand", err) + os.Exit(1) + } + return tx +}