commit 75c6ae73ad4ecb6e0f2ff5251db7c5c33b10d2de Author: Ben Wilson Date: Tue Jul 14 21:29:57 2020 -0400 Initial commit 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 +}