From 75c6ae73ad4ecb6e0f2ff5251db7c5c33b10d2de Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Tue, 14 Jul 2020 21:29:57 -0400 Subject: [PATCH] Initial commit --- .envrc | 1 + .envtemplate | 7 + .gitignore | 2 + Dockerfile | 12 ++ Makefile | 26 +++ README.md | 36 ++++ go.mod | 14 ++ go.sum | 122 +++++++++++++ main.go | 395 +++++++++++++++++++++++++++++++++++++++++ templates/zfaucet.html | 48 +++++ types.go | 30 ++++ version.go | 23 +++ zSendMany.go | 58 ++++++ 13 files changed, 774 insertions(+) create mode 100644 .envrc create mode 100644 .envtemplate create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 templates/zfaucet.html create mode 100644 types.go create mode 100644 version.go create mode 100644 zSendMany.go diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..40448e6 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +dotenv \ No newline at end of file diff --git a/.envtemplate b/.envtemplate new file mode 100644 index 0000000..f0d0283 --- /dev/null +++ b/.envtemplate @@ -0,0 +1,7 @@ +ZFAUCET_RPCUSER=zcashrpc +ZFAUCET_RPCPASSWORD=notsecure +ZFAUCET_RPCHOST=192.168.86.46 +ZFAUCET_RPCPORT=38237 +ZFAUCET_LISTENPORT=3000 +ZFAUCET_LISTENADDRESS=127.0.0.1 +ZFAUCET_FUNDINGADDRESS= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79dc7da --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +zfaucet diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a46cf7b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.13 as builder + +ADD . /app/ +WORKDIR /app/ +RUN make build + +FROM alpine:latest +RUN apk --no-cache add ca-certificates +WORKDIR /root/ +COPY --from=builder /app/zfaucet /app/zfaucet +COPY ./templates /app/templates +ENTRYPOINT ["/app/zfaucet"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..554a26f --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +SHELL := /bin/bash + +VERSION := `git describe --always` +GITCOMMIT := `git rev-parse HEAD` +BRANCH := `git rev-parse --abbrev-ref HEAD` +BUILDDATE := `date +%Y-%m-%d` +BUILDUSER := `whoami` + +LDFLAGSSTRING :=-X main.Version=$(VERSION) +LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) +LDFLAGSSTRING +=-X main.Branch=$(BRANCH) +LDFLAGSSTRING +=-X main.BuildDate=$(BUILDDATE) +LDFLAGSSTRING +=-X main.BuildUser=$(BUILDUSER) + +LDFLAGS :=-ldflags "$(LDFLAGSSTRING)" + +.PHONY: all build + +all: build + +# Build binary +build: + CGO_ENABLED=0 go build $(LDFLAGS) + +test: + go test -v ./... \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..31f1c57 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# zfaucet + +ZFaucet is software to request zcash testnet funds. + +## Requirements + +A fully synced zcashd node is required with RPC access. + +## Configuration + +All configuration is done through environmental variables. + +Copy the template file and edit the values. + +`cp .envtemplate .env` + +## Build a binary + +`make build` + +## Build Docker iamge + +`docker build .` + +## Run binary + +From the direcotry with the `.env` file. +`./zfaucet` + +## Run a Docker image + +From the direcotry with the `.env` file. +``` +docker build . -t zfaucet \ +&& docker run --env-file ./.env --rm -ti -p 3000:3000 zfaucet +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6b3c4d6 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/doubtingben/zfaucet + +go 1.13 + +require ( + github.com/gobuffalo/envy v1.9.0 // indirect + github.com/gobuffalo/packd v1.0.0 // indirect + github.com/gobuffalo/packr v1.30.1 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/lib/pq v1.7.0 + github.com/onsi/gomega v1.10.1 // indirect + github.com/rogpeppe/go-internal v1.6.0 // indirect + github.com/ybbus/jsonrpc v2.1.2+incompatible +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a705400 --- /dev/null +++ b/go.sum @@ -0,0 +1,122 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.0 h1:IZRgg4sfrDH7nsAD1Y/Nwj+GzIfEwpJSLjCaNC3SbsI= +github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/ybbus/jsonrpc v2.1.2+incompatible h1:V4mkE9qhbDQ92/MLMIhlhMSbz8jNXdagC3xBR5NDwaQ= +github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..cd9176c --- /dev/null +++ b/main.go @@ -0,0 +1,395 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/gob" + "encoding/json" + "errors" + "flag" + "fmt" + "html/template" + "log" + "net/http" + "os" + "strconv" + "time" + + "github.com/gobuffalo/packr" + "github.com/kelseyhightower/envconfig" + _ "github.com/lib/pq" + "github.com/ybbus/jsonrpc" +) + +const tapAmount = 1.0 + +type TapRequest struct { + NetworkAddress string + WalletAddress string + RequestedAt time.Time +} +type ZfaucetConfig struct { + ListenPort string + ListenAddress string + RPCUser string + RPCPassword string + RPCHost string + RPCPort string + FundingAddress string +} + +func (zConfig *ZfaucetConfig) checkConfig() error { + if zConfig.ListenPort == "" { + zConfig.ListenPort = "3000" + } + if zConfig.ListenAddress == "" { + zConfig.ListenPort = "127.0.0.1" + } + if zConfig.RPCHost == "" { + zConfig.ListenPort = "localhost" + } + if zConfig.ListenPort == "" { + zConfig.ListenPort = "3000" + } + if zConfig.FundingAddress == "" { + return fmt.Errorf("ZFAUCET_FUNDINGADDRESS is required") + } + return nil +} + +// Zfaucet holds a zfaucet configuration +type Zfaucet struct { + RPCConnetion jsonrpc.RPCClient + CurrentHeight int + UpdatedChainInfo time.Time + UpdatedWallet time.Time + Operations map[string]OperationStatus + ZcashdVersion string + ZcashNetwork string + FundingAddress string + TapRequests []*TapRequest + ZfaucetHTML string +} + +type SendAmount struct { + Address string `json:"address"` + Amount float32 `json:"amount"` +} + +// TODO tag facet transactions, zaddr targets only +type SendAmountMemo struct { + SendAmount + Memo string +} + +func (z *Zfaucet) WaitForOperation(opid string) (os OperationStatus, err error) { + var opStatus []struct { + CreationTime int `json:"creation_time"` + ID string `json:"id"` + Method string `json:"method"` + Result struct { + TxID string `json:"txid"` + } + Status string `json:"status"` + } + var parentList [][]string + var opList []string + opList = append(opList, opid) + parentList = append(parentList, opList) + fmt.Printf("opList: %s\n", opList) + fmt.Printf("parentList: %s\n", parentList) + // Wait for a few seconds for the operational status to become available + for i := 0; i < 10; i++ { + if err := z.RPCConnetion.CallFor( + &opStatus, + "z_getoperationresult", + parentList, + ); err != nil { + return os, fmt.Errorf("failed to call z_getoperationresult: %s", err) + } else { + fmt.Printf("op: %s, i: %d, status: %#v\n", opid, i, opStatus) + if len(opStatus) > 0 { + fmt.Printf("opStatus: %#v\n", opStatus[0]) + //z.Operations[opid] = OperationStatus{ + os = OperationStatus{ + UpdatedAt: time.Now(), + TxID: opStatus[0].Result.TxID, + Status: opStatus[0].Status, + } + z.Operations[opid] = os + return os, nil + } + } + time.Sleep(time.Second * 1) + } + return os, errors.New("Timeout waiting for operations status") +} + +func (z *Zfaucet) ValidateFundingAddress() (bool, error) { + if z.FundingAddress == "" { + return false, errors.New("FundingAddressis required") + } + return true, nil +} + +func (z *Zfaucet) ZSendManyFaucet(remoteAddr string, remoteWallet string) (opStatus OperationStatus, err error) { + var op *string + amountEntry := SendAmount{ + Address: remoteWallet, + Amount: tapAmount, + } + fmt.Printf("ZSendManyFaucet sending: %#v\n", amountEntry) + fmt.Printf("ZSendManyFaucet from funding address: %s\n", z.FundingAddress) + // if err != nil { + // return opStatus, err + // } + // Call z_sendmany with a single entry entry list + if err := z.RPCConnetion.CallFor( + &op, + "z_sendmany", + z.FundingAddress, + []SendAmount{amountEntry}, + ); err != nil { + return opStatus, err + } + fmt.Printf("ZSendManyFaucet sent to %s: Address: %s %s\n", remoteWallet, remoteAddr, *op) + opStatus, err = z.WaitForOperation(*op) + if err != nil { + return opStatus, err + } + if opStatus.Status != "success" { + return opStatus, fmt.Errorf("Failed to send funds: %s", err) + } + z.TapRequests = append(z.TapRequests, &TapRequest{ + NetworkAddress: remoteAddr, + WalletAddress: remoteWallet, + RequestedAt: time.Now(), + }) + return opStatus, err + +} + +type GetBlockInfo struct { + Version int +} + +func getBlockchainInfo(rpcClient jsonrpc.RPCClient) (blockChainInfo *GetBlockchainInfo, err error) { + if err := rpcClient.CallFor(&blockChainInfo, "getblockchaininfo"); err != nil { + return nil, err + } + return +} + +func getInfo(rpcClient jsonrpc.RPCClient) (info *GetBlockInfo, err error) { + if err := rpcClient.CallFor(&info, "getinfo"); err != nil { + return nil, err + } + return info, nil +} + +func main() { + versionFlag := flag.Bool("version", false, "print version information") + flag.Parse() + if *versionFlag { + fmt.Printf("(version=%s, branch=%s, gitcommit=%s)\n", Version, Branch, GitCommit) + fmt.Printf("(go=%s, user=%s, date=%s)\n", GoVersion, BuildUser, BuildDate) + os.Exit(0) + } + + var zConfig ZfaucetConfig + err := envconfig.Process("zfaucet", &zConfig) + if err != nil { + log.Fatal(err.Error()) + } + if err = zConfig.checkConfig(); err != nil { + log.Fatalf("Config error: %s", err) + } + fmt.Printf("zfaucet: %#v\n", zConfig) + + basicAuth := base64.StdEncoding.EncodeToString([]byte(zConfig.RPCUser + ":" + zConfig.RPCPassword)) + var z Zfaucet + z.FundingAddress = zConfig.FundingAddress + z.Operations = make(map[string]OperationStatus) + z.RPCConnetion = jsonrpc.NewClientWithOpts("http://"+zConfig.RPCHost+":"+zConfig.RPCPort, + &jsonrpc.RPCClientOpts{ + CustomHeaders: map[string]string{ + "Authorization": "Basic " + basicAuth, + }}) + + zChainInfo, err := getBlockchainInfo(z.RPCConnetion) + if err != nil { + log.Fatalf("Failed to get blockchaininfo: %s", err) + } + z.CurrentHeight = zChainInfo.Blocks + z.ZcashNetwork = zChainInfo.Chain + zVersion, err := getInfo(z.RPCConnetion) + if err != nil { + log.Fatalf("Failed to getinfo: %s", err) + } + z.ZcashdVersion = strconv.Itoa(zVersion.Version) + + box := packr.NewBox("./templates") + z.ZfaucetHTML, err = box.FindString("zfaucet.html") + if err != nil { + log.Fatal(err) + } + homeHandler := http.HandlerFunc(z.home) + balanceHandler := http.HandlerFunc(z.balance) + opsStatusHandler := http.HandlerFunc(z.opsStatus) + addressHandler := http.HandlerFunc(z.addresses) + mux := http.NewServeMux() + mux.Handle("/", homeHandler) + mux.Handle("/balance", z.OKMiddleware(balanceHandler)) + mux.Handle("/addresses", z.OKMiddleware(addressHandler)) + mux.Handle("/ops/status", z.OKMiddleware(opsStatusHandler)) + log.Printf("Listening on :%s...\n", zConfig.ListenPort) + err = http.ListenAndServe(zConfig.ListenAddress+":"+zConfig.ListenPort, mux) + log.Fatal(err) +} + +// OperationStatus describes an rpc response +type OperationStatus struct { + UpdatedAt time.Time + Status string + TxID string + result interface{} +} + +// home is the default request handler +func (z *Zfaucet) home(w http.ResponseWriter, r *http.Request) { + // tData is the html template data + tData := struct { + Z *Zfaucet + Msg string + }{ + z, + "", + } + switch r.Method { + case http.MethodPost: + if err := checkFaucetAddress(r.FormValue("address")); err != nil { + tData.Msg = fmt.Sprintf("Invalid address: %s", err) + break + } + opStatus, err := z.ZSendManyFaucet(r.RemoteAddr, r.FormValue("address")) + if err != nil { + tData.Msg = fmt.Sprintf("Failed to send funds: %s", err) + break + } + tData.Msg = fmt.Sprintf("Successfully submitted operation: %s", opStatus) + } + w.Header().Set("Content-Type", "text/html") + tmpl, err := template.New("name").Parse(z.ZfaucetHTML) + if err != nil { + http.Error(w, err.Error(), 500) + } + tmpl.Execute(w, tData) +} + +// OKMiddleware determines if a request is allowed before execution +func (z *Zfaucet) OKMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Our middleware logic goes here... + next.ServeHTTP(w, r) + }) +} + +// Balance +func (z *Zfaucet) balance(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var totalBalance *z_gettotalbalance + if err := z.RPCConnetion.CallFor(&totalBalance, "z_gettotalbalance"); err != nil { + http.Error(w, err.Error(), 500) + return + } + out, err := json.Marshal(totalBalance) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + fmt.Fprintf(w, string(out)) +} + +// opsStatus +func (z *Zfaucet) opsStatus(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var ops *[]string + if err := z.RPCConnetion.CallFor(&ops, "z_listoperationids"); err != nil { + http.Error(w, err.Error(), 500) + return + } + out, err := json.Marshal(ops) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + fmt.Fprintf(w, string(out)) +} + +// addresses +func (z *Zfaucet) addresses(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var addresses []WalletAddress + var zlist *[]string + var taddrs []interface{} + // Z addresses + if err := z.RPCConnetion.CallFor(&zlist, "z_listaddresses"); err != nil { + http.Error(w, err.Error(), 500) + return + } + for _, zaddr := range *zlist { + entry := WalletAddress{ + Address: zaddr, + } + entry.Notes = append(entry.Notes, "z address") + addresses = append(addresses, entry) + + } + // T addresses + if err := z.RPCConnetion.CallFor(&taddrs, "listaddressgroupings"); err != nil { + http.Error(w, fmt.Sprintf("Problem calling listaddressgroupings: %s", err.Error()), 500) + return + } + fmt.Printf("T addresses:\n%#v\n", taddrs) + // TODO: fix this mess + for _, a := range taddrs { + switch aResult := a.(type) { + case []interface{}: + for _, b := range aResult { + switch bResult := b.(type) { + case []interface{}: + for _, x := range bResult { + switch x.(type) { + case string: + taddr := fmt.Sprintf("%v", x) + fmt.Printf("Adding T Address: %s\n", taddr) + entry := WalletAddress{ + Address: taddr, + } + entry.Notes = append(entry.Notes, "t address") + addresses = append(addresses, entry) + } + } + } + } + } + } + out, err := json.Marshal(addresses) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + fmt.Fprintf(w, string(out)) +} + +// GetBytes returns a byte slice from an interface +func GetBytes(key interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(key) + if err != nil { + return nil, err + } + return buf.Bytes(), nil + +} diff --git a/templates/zfaucet.html b/templates/zfaucet.html new file mode 100644 index 0000000..69a134a --- /dev/null +++ b/templates/zfaucet.html @@ -0,0 +1,48 @@ +

zFaucet

+ + +
+
+ + +
+
+ {{.Msg}} +
+
+

Tap Requests

+ {{range $k, $v := .Z.TapRequests}} +
+ NetworkAddress: {{$v.NetworkAddress}}
+ WalletAddress: {{$v.WalletAddress}}
+ Requested at: {{$v.RequestedAt.Format "Mon, 02 Jan 2006 15:04:05 MST "}}
+
+
+ {{end}} +
+
+
+

Operations

+ {{range $k, $v := .Z.Operations}} +
+ Updated at: {{$v.UpdatedAt.Format "Mon, 02 Jan 2006 15:04:05 MST "}}
+ opid: {{$k}}
+ Status: {{$v.Status}}
+ Transaction ID: {{$v.TxID}}
+
+
+ {{end}} +
+ \ No newline at end of file diff --git a/types.go b/types.go new file mode 100644 index 0000000..dcbe19e --- /dev/null +++ b/types.go @@ -0,0 +1,30 @@ +package main + +// GetBlockchainInfo return the zcashd rpc `getblockchaininfo` status +// https://zcash-rpc.github.io/getblockchaininfo.html +type GetBlockchainInfo struct { + Chain string `json:"chain"` + Blocks int `json:"blocks"` + Headers int `json:"headers"` + BestBlockhash string `json:"bestblockhash"` + Difficulty float64 `json:"difficulty"` + VerificationProgress float64 `json:"verificationprogress"` + SizeOnDisk float64 `json:"size_on_disk"` + SoftForks []SoftFork `json:"softforks"` +} + +type SoftFork struct { + ID string `json:"id"` + Version int `json:"version"` +} + +type z_gettotalbalance struct { + Transparent string + Private string + Total string +} + +type WalletAddress struct { + Address string + Notes []string +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..1f05ce7 --- /dev/null +++ b/version.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "runtime" +) + +var ( + Version string + GitCommit string + Branch string + BuildUser string + BuildDate string + GoVersion = runtime.Version() +) + +func Info() string { + return fmt.Sprintf("(version=%s, branch=%s, gitcommit=%s)", Version, Branch, GitCommit) +} + +func BuildContext() string { + return fmt.Sprintf("(go=%s, user=%s, date=%s)", GoVersion, BuildUser, BuildDate) +} \ No newline at end of file diff --git a/zSendMany.go b/zSendMany.go new file mode 100644 index 0000000..b2073f3 --- /dev/null +++ b/zSendMany.go @@ -0,0 +1,58 @@ +package main + +import ( + "errors" + "fmt" + "net/http" + "regexp" +) + +func zSendManyHTTPPost(r *http.Request) (opid string, err error) { + fmt.Printf("zSendManyHTTPPost address: %s\n", r.FormValue("address")) + switch { + case r.FormValue("address") == "": + fmt.Println("address blank case") + return "", errors.New("Form field value required: address") + case isTestnetTransparent(r.FormValue("address")): + fmt.Println("Address is a transparent testnet address") + + return r.FormValue("address"), nil + + default: + fmt.Println("address default case") + return "", errors.New("A valid address is required") + } +} + +func isTestnetTransparent(addr string) bool { + //TODO Check length and encoding + matched, _ := regexp.MatchString(`^tm`, addr) + return matched +} + +func isTestnetSaplingZaddr(addr string) bool { + //TODO Check length and encoding + matched, _ := regexp.MatchString(`^ztestsapling`, addr) + return matched +} + +func checkFaucetAddress(checkAddr string) error { + switch { + case checkAddr == "": + fmt.Println("address blank case") + return errors.New("Form field value required: address") + case isTestnetTransparent(checkAddr): + fmt.Println("Address is a testnet transparent address") + return nil + case isTestnetSaplingZaddr(checkAddr): + fmt.Println("Address is a testnet sapling address") + return nil + default: + fmt.Println("address default case") + return errors.New("A valid address is required") + } +} + +func zSendManyFaucet(addr string) (opid string, err error) { + return "000-test-opid-string-000", nil +}