From ff8e8cc3773e6f3b1dce026fc3e9a7540a1c37d9 Mon Sep 17 00:00:00 2001 From: Massaki Archambault Date: Sat, 25 Sep 2021 15:09:40 -0400 Subject: [PATCH] add deposit/withdraw --- .dockerignore | 1 + .drone.yml | 17 +++ Dockerfile | 10 ++ Makefile | 33 ++--- helium-blockchain-exporter.go => exporter.go | 110 +++++++++++----- heliumapi/activity/activities.go | 55 +++++++- heliumapi/activity/types.go | 128 +++++++++++++++++++ heliumapi/types.go | 6 - 8 files changed, 297 insertions(+), 63 deletions(-) create mode 100644 .dockerignore create mode 100644 .drone.yml create mode 100644 Dockerfile rename helium-blockchain-exporter.go => exporter.go (86%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6dd29b7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +bin/ \ No newline at end of file diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..2cfd37b --- /dev/null +++ b/.drone.yml @@ -0,0 +1,17 @@ +kind: pipeline +type: kubernetes +name: arm64 + +node_selector: + beta.kubernetes.io/arch: arm64 + +steps: +- name: docker + image: plugins/docker + settings: + repo: badjware/nextcloud-tweak + auto_tag: true + username: + from_secret: docker_username + password: + from_secret: docker_password \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..392ca53 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:1.17-alpine as builder +COPY . /app +WORKDIR /app +RUN apk add git make build-base \ + && make dist + +FROM gcr.io/distroless/base-debian10 +COPY --from=builder /app/bin/helium-blockchain-exporter /usr/bin/helium-blockchain-exporter +EXPOSE 9865 +ENTRYPOINT [ "/usr/bin/helium-blockchain-exporter" ] \ No newline at end of file diff --git a/Makefile b/Makefile index 945833f..5dd310f 100644 --- a/Makefile +++ b/Makefile @@ -33,19 +33,19 @@ all: build help: @echo "clean revert back to clean state" @echo "lint lint and format the code" - @echo "run run without building" @echo "prepare prepare environment for build" @echo "test build and run short test suite" @echo "coverage build and run full test suite and generate coverage report" @echo "build build the binary" - @echo "build-docker build the docker image" + @echo "dist build the binary for release" + @echo "docker build the docker image" # clean: revert back to initial state .PHONY: clean clean: $(GOCLEAN) - rm -r $(TARGET_DIR) + rm -r $(TARGET_DIR) || true # lint: lint and format the code @@ -53,17 +53,6 @@ clean: lint: $(GOFMT) ./... -# run: run without building with debug flags -ifeq (run,$(firstword $(MAKECMDGOALS))) - # use the rest as arguments for "run" - RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) - # ...and turn them into do-nothing targets - $(eval $(RUN_ARGS):;@:) -endif -.PHONY: run -run: lint - $(GORUN) . $(RUN_ARGS) - # prepare: prepare environment for build $(TARGET_DIR): @@ -90,18 +79,24 @@ $(COVER_REPORT): $(TARGET_DIR) $(COVER_PROFILE) coverage: lint $(COVER_REPORT) $(GOCOVER) -func=$(COVER_PROFILE) -# build: build the program for release +# build: build the program $(BIN): $(TARGET_DIR) $(SRC) - $(GOBUILD) -o=$(BIN) + $(GOBUILD) -o=$(BIN) -race .PHONY: build build: lint $(BIN) -# build-docker: build the docker image +# dist: build the program for release -.PHONY: build-docker -build-docker: $(BIN) +.PHONY: dist +dist: $(TARGET_DIR) $(SRC) + $(GOBUILD) -o=$(BIN) -a -ldflags "-linkmode external -extldflags -static" + +# docker: build the docker image + +.PHONY: docker +docker: $(BIN) $(DOCKERBUILD) -t $(PROGRAM) . $(foreach tag,$(DOCKER_TAGS),$(DOCKERTAG) $(PROGRAM) $(tag);) diff --git a/helium-blockchain-exporter.go b/exporter.go similarity index 86% rename from helium-blockchain-exporter.go rename to exporter.go index 7c37666..ef4e5db 100644 --- a/helium-blockchain-exporter.go +++ b/exporter.go @@ -53,7 +53,6 @@ func NewAccount(address string) Account { DepositTotal: 0, WithdrawalTotal: 0, LastUpdate: time.Now(), - // LastUpdate: time.Time{}, }, } } @@ -69,23 +68,6 @@ const ( ) var ( - // Activity classification - accountDepositActivities = []string{ - "payment_v1", // if payee == account_address - "payment_v2", // if payee == account_address - "rewards_v1", - "unstake_validator_v1", - } - accountWithdrawalActivities = []string{ - "add_gateway_v1", - "assert_location_v1", - "assert_location_v2", - "payment_v1", // if payer == account_address - "payment_v2", // if payer == account_address - "stake_validator_v1", - "token_burn_v1", - } - // labels commonAccountLabels = []string{"account"} commonHotspotLabels = append(commonAccountLabels, "hotspot", "hotspot_name") @@ -415,7 +397,7 @@ func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *A fmt.Println(err) return } - tx, err := account.computeTransactions() + err = account.collectTransactionMetrics(ch) if err != nil { fmt.Println(err) return @@ -435,14 +417,6 @@ func (e *Exporter) collectAccountMetrics(ch chan<- prometheus.Metric, account *A accountRewardsHnt.Desc, accountRewardsHnt.Type, accountRewardTotalsForAddress.Data.Sum, account.Address, ) - ch <- prometheus.MustNewConstMetric( - accountDepositsHnt.Desc, accountDepositsHnt.Type, float64(tx.DepositTotal), - account.Address, - ) - ch <- prometheus.MustNewConstMetric( - accountWithdrawalsHnt.Desc, accountWithdrawalsHnt.Type, float64(tx.WithdrawalTotal), - account.Address, - ) } // collectStatsMetrics collect metrics in the hotspot group from the helium api @@ -503,24 +477,90 @@ func (e *Exporter) collectHotspotMetrics(ch chan<- prometheus.Metric, account *A } } -func (a *Account) computeTransactions() (*AccountTx, error) { +func (a *Account) collectTransactionMetrics(ch chan<- prometheus.Metric) error { now := time.Now() - _, err := heliumapi.GetActivityForAccount(a.Address, []string{}, &a.Tx.LastUpdate, &now) + activities, err := heliumapi.GetActivityForAccount(a.Address, []string{}, &a.Tx.LastUpdate, &now) if err != nil { - return nil, err + return err } - // fmt.Println(activities) - + // logic based on https://github.com/helium/hotspot-app/blob/918563fba84d1abf4554a43a4d42bb838d017bd3/src/features/wallet/root/useActivityItem.tsx#L336 + for _, activity := range activities.AddGatewayV1 { + a.Tx.WithdrawalTotal += activity.StakingFee + } + for _, activity := range activities.AssertLocationV1 { + a.Tx.WithdrawalTotal += activity.StakingFee + } + for _, activity := range activities.AssertLocationV2 { + a.Tx.WithdrawalTotal += activity.StakingFee + } + for _, activity := range activities.PaymentV1 { + if activity.Payer == a.Address { + a.Tx.WithdrawalTotal += activity.Amount + } else { + a.Tx.DepositTotal += activity.Amount + } + } + for _, activity := range activities.PaymentV2 { + if activity.Payer == a.Address { + paymentTotal := 0 + for _, payment := range activity.Payments { + paymentTotal += payment.Amount + } + a.Tx.WithdrawalTotal += paymentTotal + } else { + for _, payment := range activity.Payments { + if payment.Payee == a.Address { + a.Tx.DepositTotal += payment.Amount + } + } + } + } + for _, activity := range activities.RewardsV1 { + for _, reward := range activity.Rewards { + a.Tx.DepositTotal += reward.Amount + } + } + for _, activity := range activities.RewardsV2 { + for _, reward := range activity.Rewards { + a.Tx.DepositTotal += reward.Amount + } + } + for _, activity := range activities.StakeValidatorV1 { + a.Tx.WithdrawalTotal += activity.Stake + } + for _, activity := range activities.TokenBurnV1 { + a.Tx.WithdrawalTotal += activity.Amount + } + for _, activity := range activities.TransferHotspotV1 { + if activity.Buyer == a.Address { + a.Tx.WithdrawalTotal += activity.AmountToSeller + } else { + a.Tx.DepositTotal += activity.AmountToSeller + } + } + for _, activity := range activities.UnstakeValidatorV1 { + a.Tx.WithdrawalTotal += activity.StakeAmount + } a.Tx.LastUpdate = now - return &a.Tx, nil + + ch <- prometheus.MustNewConstMetric( + accountDepositsHnt.Desc, accountDepositsHnt.Type, float64(a.Tx.DepositTotal), + a.Address, + ) + ch <- prometheus.MustNewConstMetric( + accountWithdrawalsHnt.Desc, accountWithdrawalsHnt.Type, float64(a.Tx.WithdrawalTotal), + a.Address, + ) + + return nil } func main() { - fHeliumAccounts := flag.String("accounts", "", "A comma-delimited list of helium accounts to scrape.") + fHeliumAccounts := flag.String("accounts", "", "A comma-delimited list of helium accounts to scrape (optional)") fMetricsPath := flag.String("metricpath", "/metrics", "The metrics path") fListenAddress := flag.String("listenAddress", "0.0.0.0", "The http server listen address") - fListenPort := flag.String("listenPort", "9111", "The http server listen port") + fListenPort := flag.String("listenPort", "9865", "The http server listen port") flag.Parse() heliumAccounts := strings.Split(*fHeliumAccounts, ",") diff --git a/heliumapi/activity/activities.go b/heliumapi/activity/activities.go index 804c335..708c0ec 100644 --- a/heliumapi/activity/activities.go +++ b/heliumapi/activity/activities.go @@ -9,8 +9,6 @@ type ActivityResp struct { Data []json.RawMessage `json:"data"` } -type Activities struct{} - func NewActivities(resp ActivityResp) (*Activities, error) { type ActivityType struct { Type string `json:"type"` @@ -26,16 +24,67 @@ func NewActivities(resp ActivityResp) (*Activities, error) { fmt.Println(activityType.Type) switch activityType.Type { case "add_gateway_v1": + addGatewayV1 := AddGatewayV1{} + if err := json.Unmarshal(activityRaw, &addGatewayV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.AddGatewayV1 = append(activities.AddGatewayV1, addGatewayV1) case "assert_location_v1": + assertLocationV1 := AssertLocationV1{} + if err := json.Unmarshal(activityRaw, &assertLocationV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.AssertLocationV1 = append(activities.AssertLocationV1, assertLocationV1) case "assert_location_v2": + assertLocationV2 := AssertLocationV2{} + if err := json.Unmarshal(activityRaw, &assertLocationV2); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.AssertLocationV2 = append(activities.AssertLocationV2, assertLocationV2) case "payment_v1": + paymentV1 := PaymentV1{} + if err := json.Unmarshal(activityRaw, &paymentV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.PaymentV1 = append(activities.PaymentV1, paymentV1) case "payment_v2": + paymentV2 := PaymentV2{} + if err := json.Unmarshal(activityRaw, &paymentV2); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.PaymentV2 = append(activities.PaymentV2, paymentV2) case "rewards_v1": + rewardV1 := RewardsV1{} + if err := json.Unmarshal(activityRaw, &rewardV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.RewardsV1 = append(activities.RewardsV1, rewardV1) + case "rewards_v2": + rewardV2 := RewardsV2{} + if err := json.Unmarshal(activityRaw, &rewardV2); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.RewardsV2 = append(activities.RewardsV2, rewardV2) case "stake_validator_v1": + stakeValidatorV1 := StakeValidatorV1{} + if err := json.Unmarshal(activityRaw, &stakeValidatorV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.StakeValidatorV1 = append(activities.StakeValidatorV1, stakeValidatorV1) case "token_burn_v1": + tokenBurnV1 := TokenBurnV1{} + if err := json.Unmarshal(activityRaw, &activities.TokenBurnV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.TokenBurnV1 = append(activities.TokenBurnV1, tokenBurnV1) case "unstake_validator_v1": + unstakeValidatorV1 := UnstakeValidatorV1{} + if err := json.Unmarshal(activityRaw, &activities.UnstakeValidatorV1); err != nil { + return nil, fmt.Errorf("failed to unmarshal %v: %v", activityType.Type, err) + } + activities.UnstakeValidatorV1 = append(activities.UnstakeValidatorV1, unstakeValidatorV1) default: - fmt.Printf("unimplemented activy type: %v", activityType.Type) + fmt.Printf("ignoring unimplemented activy type: %v", activityType.Type) } } diff --git a/heliumapi/activity/types.go b/heliumapi/activity/types.go index 6609310..737f0cf 100644 --- a/heliumapi/activity/types.go +++ b/heliumapi/activity/types.go @@ -1 +1,129 @@ package activity + +type Activities struct { + AddGatewayV1 []AddGatewayV1 + AssertLocationV1 []AssertLocationV1 + AssertLocationV2 []AssertLocationV2 + PaymentV1 []PaymentV1 + PaymentV2 []PaymentV2 + RewardsV1 []RewardsV1 + RewardsV2 []RewardsV2 + StakeValidatorV1 []StakeValidatorV1 + TokenBurnV1 []TokenBurnV1 + TransferHotspotV1 []TransferHotspotV1 + UnstakeValidatorV1 []UnstakeValidatorV1 +} + +type AddGatewayV1 struct { + Hash string `json:"hash"` + Fee int `json:"fee"` + Owner string `json:"owner"` + Payer string `json:"payer"` + Gateway string `json:"gateway"` + StakingFee int `json:"staking_fee"` +} + +type AssertLocationV1 struct { + Hash string `json:"hash"` + Fee int `json:"fee"` + Nonce int `json:"nonce"` + Owner string `json:"owner"` + Payer string `json:"payer,omitempty"` + Gateway string `json:"gateway"` + Location string `json:"location"` + StakingFee int `json:"staking_fee"` +} + +type AssertLocationV2 struct { + Hash string `json:"hash"` + Fee int `json:"fee"` + Gain int `json:"gain"` + Nonce int `json:"nonce"` + Owner string `json:"owner"` + Payer string `json:"payer,omitempty"` + Gateway string `json:"gateway"` + Location string `json:"location"` + Elevation int `json:"elevation"` + StakingFee int `json:"staking_fee"` +} + +type PaymentV1 struct { + Hash string `json:"hash"` + Amount int `json:"amount"` + Fee int `json:"fee"` + Nonce int `json:"nonce"` + Payer string `json:"payer"` + Payee string `json:"payee"` +} + +type PaymentV2 struct { + Hash string `json:"hash"` + Fee int `json:"fee"` + Nonce int `json:"nonce"` + Payer string `json:"payer"` + Payments []struct { + Amount int `json:"amount"` + Memo string `json:"memo"` + Payee string `json:"payee"` + } +} + +type reward struct { + Account string `json:"account,omitempty"` + Amount int `json:"amount"` + Gateway string `json:"gateway,omitempty"` + Type string `json:"type"` +} + +type RewardsV1 struct { + Hash string `json:"hash"` + StartEpoch int `json:"start_epoch"` + EndEpoch int `json:"end_epoch"` + Rewards []reward `json:"rewards"` +} + +type RewardsV2 struct { + Hash string `json:"hash"` + StartEpoch int `json:"start_epoch"` + EndEpoch int `json:"end_epoch"` + Rewards []reward `json:"rewards"` +} + +type StakeValidatorV1 struct { + Address string `json:"address"` + Fee int `json:"fee"` + Hash string `json:"hash"` + Owner string `json:"owner"` + Stake int `json:"stake"` + OwnerSignature string `json:"owner_signature"` +} + +type TokenBurnV1 struct { + Fee int `json:"fee"` + Hash string `json:"hash"` + Memo string `json:"memo"` + Nonce int `json:"nonce"` + Payer string `json:"payer"` + Payee string `json:"payee"` + Amount int `json:"amount"` +} + +type TransferHotspotV1 struct { + Hash string `json:"hash"` + Fee int `json:"fee"` + Buyer string `json:"buyer"` + Seller string `json:"seller"` + Gateway string `json:"gateway"` + BuyerNonce int `json:"buyer_nonce"` + AmountToSeller int `json:"amount_to_seller"` +} + +type UnstakeValidatorV1 struct { + Address string `json:"address"` + Owner string `json:"owner"` + OwnerSignature string `json:"owner_signature"` + Fee int `json:"fee"` + StakeAmount int `json:"stake_amount"` + StakeReleaseHeight int `json:"stake_release_height"` + Hash string `json:"hash"` +} diff --git a/heliumapi/types.go b/heliumapi/types.go index 087f5d6..5c41228 100644 --- a/heliumapi/types.go +++ b/heliumapi/types.go @@ -13,12 +13,6 @@ type Account struct { } `json:"data"` } -// Time int `json:"time"` -// StartEpoch int `json:"start_epoch"` -// Height int `json:"height"` -// Hash string `json:"hash"` -// EndEpoch int `json:"end_epoch"` - type AccountHotspots struct { Data []struct { Lng float64 `json:"lng"`