Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate census3 api client #50

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ VOCDONI_WEB3=https://mainnet.optimism.io,https://optimism.llamarpc.com,https://o
# VOCDONI_AIRSTACKBLOCKCHAINS="ethereum,polygon,base"
# VOCDONI_AIRSTACKSUPPORTAPIENDPOINT=
# VOCDONI_AIRSTACKMAXHOLDERS=10000
# VOCDOIN_AIRSTACKTOKENWHITELIST=""
# VOCDOIN_AIRSTACKTOKENWHITELIST=""

# VOCDONI_CENSUS3ENDPOINT=""
# VOCDONI_CENSUS3APIKEY=""
151 changes: 151 additions & 0 deletions census3/census3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package census3

import (
"fmt"
"net/url"
"time"

"github.com/google/uuid"
c3types "github.com/vocdoni/census3/api"
c3client "github.com/vocdoni/census3/apiclient"
)

const (
// maxRetries is the maximum number of retries for a request.
maxRetries = 3
// retryTime is the time to wait between retries.
retryTime = time.Second * 2
)

// Client wraps a client for the Census3 API and other revelant data.
type Client struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh I'm not sure for the need of this wrapper of the census3 client (which is already a wrapper for the API).
The retry mechanism could also be implemented on the original client instead of here.

What's the point @jordipainan ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to wrap these kind of imports so they can be extended on the consumer, imo adds more flexibility i.e: a new attribute needs to be added.

I dont think the retry mechanism should be added on the base client, and I think thats up to the consumer too. The base client needs to be simplest as possible and the retry mechanism is something one may decide to add or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the methods for the wrapper are just calls to the census3 client with the retry helper. I still don't see the reason to have this wrapper, tbh.

Adding a wrapper adds complexity, and I'm not sure about the flexibility thing. Keep it simple if possible.

Anyway, let's call for more feedback @lucasmenendez

c *c3client.HTTPclient
}

// NewClient creates a new client for the Census3 API.
func NewClient(endpoint string, bearerToken string) (*Client, error) {
bt, err := uuid.Parse(bearerToken)
if err != nil {
return nil, fmt.Errorf("invalid bearer token: %v", err)
}
addr, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("invalid endpoint: %v", err)
}
httpClient, err := c3client.NewHTTPclient(addr, &bt)
if err != nil {
return nil, fmt.Errorf("error creating HTTP client: %v", err)
}
c3 := &Client{
c: httpClient,
}
return c3, nil
}

// reqFunc is a function type that encapsulates an API call
type reqFunc[T any] func() (T, error)

// requestWithRetry handles the retry logic for a request
func requestWithRetry[T any](fn reqFunc[T]) (T, error) {
var result T
for i := 0; i < maxRetries; i++ {
result, err := fn()
if err != nil {
time.Sleep(retryTime)
continue
}
return result, nil
}
return result, fmt.Errorf("failed after %d retries", maxRetries)
}

// SupportedChains returns the information of the Census3 endpoint supported chains.
func (c3 *Client) SupportedChains() ([]c3types.SupportedChain, error) {
return requestWithRetry(func() ([]c3types.SupportedChain, error) {
info, err := c3.c.Info()
if err != nil {
return nil, fmt.Errorf("failed to get supported chains: %w", err)
}
return info.SupportedChains, nil
})
}

// Tokens returns the list of tokens registered in the Census3 endpoint.
func (c3 *Client) Tokens() ([]*c3types.TokenListItem, error) {
return requestWithRetry(func() ([]*c3types.TokenListItem, error) {
tokens, err := c3.c.Tokens(-1, "", "")
if err != nil {
return nil, fmt.Errorf("failed to get tokens: %w", err)
}
return tokens, nil

})
}

// Token returns the token with the given ID.
func (c3 *Client) Token(tokenID, externalID string, chainID uint64) (*c3types.Token, error) {
return requestWithRetry(func() (*c3types.Token, error) {
token, err := c3.c.Token(tokenID, chainID, externalID)
if err != nil {
return nil, fmt.Errorf("failed to get token: %w", err)
}
return token, nil
})
}

// SupportedTokens returns the list of tokens supported by the Census3 endpoint.
func (c3 *Client) SupportedTokens() ([]string, error) {
return requestWithRetry(func() ([]string, error) {
tokens, err := c3.c.TokenTypes()
if err != nil {
return nil, fmt.Errorf("failed to get supported tokens: %w", err)
}
return tokens, nil
})
}

// Strategies returns the list of strategies registered in the Census3 endpoint.
func (c3 *Client) Strategies() ([]*c3types.Strategy, error) {
return requestWithRetry(func() ([]*c3types.Strategy, error) {
strategies, err := c3.c.Strategies(-1, "", "")
if err != nil {
return nil, fmt.Errorf("failed to get strategies: %w", err)
}
return strategies, nil
})
}

// Strategy returns the strategy with the given ID.
func (c3 *Client) Strategy(strategyID uint64) (*c3types.Strategy, error) {
return requestWithRetry(func() (*c3types.Strategy, error) {
strategy, err := c3.c.Strategy(strategyID)
if err != nil {
return nil, fmt.Errorf("failed to get strategy: %w", err)
}
return strategy, nil
})
}

// StrategyHolders returns the list of holders for the given strategy.
// The returned map has the holder's address as key and the holder's balance as a string encoded big.Int
func (c3 *Client) StrategyHolders(strategyID uint64) (map[string]string, error) {
return requestWithRetry(func() (map[string]string, error) {
holders, err := c3.c.HoldersByStrategy(strategyID)
if err != nil {
return nil, fmt.Errorf("failed to get strategy holders: %w", err)
}
return holders.Holders, nil
})
}

// Census returns the census with the given ID.
func (c3 *Client) Census(censusID uint64) (*c3types.Census, error) {
return requestWithRetry(func() (*c3types.Census, error) {
census, err := c3.c.Census(censusID)
if err != nil {
return nil, fmt.Errorf("failed to get census: %w", err)
}
return census, nil

})
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
github.com/vocdoni/census3 v0.1.4-0.20240411135514-e434723b7840
github.com/zeebo/blake3 v0.2.3
go.mongodb.org/mongo-driver v1.14.0
go.vocdoni.io/dvote v1.10.2-0.20240313095944-f5790a5af0ed
Expand All @@ -21,6 +22,7 @@ require (
require (
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512 // indirect
contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9 // indirect
github.com/766b/chi-prometheus v0.0.0-20211217152057-87afa9aa2ca8 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/DataDog/zstd v1.5.2 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9 h1:Ahny8Ud1LjVMMAlt8utUFKhhxJtwBAualvsbc/Sk7cE=
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA=
github.com/766b/chi-prometheus v0.0.0-20211217152057-87afa9aa2ca8 h1:hK1G69lDhhrGqJbRA5i1rmT2KI/W77MSdr7hEGHqWdQ=
github.com/766b/chi-prometheus v0.0.0-20211217152057-87afa9aa2ca8/go.mod h1:X/LhbmoBoRu8TxoGIOIraVNhfz3hhikJoaelrOuhdPY=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
Expand Down Expand Up @@ -1545,6 +1548,8 @@ github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUO
github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vocdoni/census3 v0.1.4-0.20240411135514-e434723b7840 h1:1iw2/8sSZAyXioxG7y4bf7D59DVrFtfYuVu1UNoocps=
github.com/vocdoni/census3 v0.1.4-0.20240411135514-e434723b7840/go.mod h1:Ml5LlJQLbbd5GYttVCKu4kN2mjvrdLSxXfjoc/okNRY=
github.com/vocdoni/storage-proofs-eth-go v0.1.6 h1:mF6VNGudgCjjgjxs5Z2GGMM2tS79+xFBWcr7BnPuhAY=
github.com/vocdoni/storage-proofs-eth-go v0.1.6/go.mod h1:aoD9pKg8kDFQ5I6b3obGPTwjC+DsaW2h4wt2UQEjQrg=
github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE=
Expand Down
4 changes: 4 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/google/uuid"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/vocdoni/vote-frame/airstack"
"github.com/vocdoni/vote-frame/census3"
"github.com/vocdoni/vote-frame/farcasterapi"
"github.com/vocdoni/vote-frame/imageframe"
"github.com/vocdoni/vote-frame/mongo"
Expand All @@ -38,6 +39,7 @@ type vocdoniHandler struct {
electionLRU *lru.Cache[string, *api.Election]
fcapi farcasterapi.API
airstack *airstack.Airstack
census3 *census3.Client

censusCreationMap sync.Map
addAuthTokenFunc func(uint64, string)
Expand All @@ -53,6 +55,7 @@ func NewVocdoniHandler(
fcapi farcasterapi.API,
token *uuid.UUID,
airstack *airstack.Airstack,
census3 *census3.Client,
) (*vocdoniHandler, error) {
// Get the vocdoni account
if accountPrivKey == "" {
Expand Down Expand Up @@ -89,6 +92,7 @@ func NewVocdoniHandler(
db: db,
fcapi: fcapi,
airstack: airstack,
census3: census3,
electionLRU: func() *lru.Cache[string, *api.Election] {
lru, err := lru.New[string, *api.Election](100)
if err != nil {
Expand Down
31 changes: 30 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/vocdoni/vote-frame/airstack"
"github.com/vocdoni/vote-frame/census3"
"github.com/vocdoni/vote-frame/discover"
"github.com/vocdoni/vote-frame/farcasterapi"
"github.com/vocdoni/vote-frame/farcasterapi/hub"
Expand Down Expand Up @@ -81,6 +82,10 @@ func main() {
flag.String("airstackSupportAPIEndpoint", "", "Airstack support API endpoint")
flag.String("airstackTokenWhitelist", "", "Airstack token whitelist")

// Census3 flags
flag.String("census3APIEndpoint", "https://census3.vocdoni.net", "The Census3 API endpoint to use")
flag.String("census3APIToken", "", "The Census3 API token to use")

// Limited features flags
flag.Int32("featureNotificationReputation", 15, "Reputation threshold to enable the notification feature")

Expand Down Expand Up @@ -134,6 +139,10 @@ func main() {
airstackSupportAPIEndpoint := viper.GetString("airstackSupportAPIEndpoint")
airstackTokenWhitelist := viper.GetString("airstackTokenWhitelist")

// census3 vars
census3APIEndpoint := viper.GetString("census3APIEndpoint")
census3APIToken := viper.GetString("census3APIToken")

// limited features vars
featureNotificationReputation := uint32(viper.GetInt32("featureNotificationReputation"))

Expand Down Expand Up @@ -264,9 +273,29 @@ func main() {
}
}

// Create Census3 client
var c3 *census3.Client
if census3APIEndpoint != "" {
c3, err = census3.NewClient(census3APIEndpoint, census3APIToken)
if err != nil {
log.Fatal(err)
}
}

// Create the Vocdoni handler
apiTokenUUID := uuid.MustParse(apiToken)
handler, err := NewVocdoniHandler(apiEndpoint, vocdoniPrivKey, censusInfo, webAppDir, db, mainCtx, neynarcli, &apiTokenUUID, as)
handler, err := NewVocdoniHandler(
apiEndpoint,
vocdoniPrivKey,
censusInfo,
webAppDir,
db,
mainCtx,
neynarcli,
&apiTokenUUID,
as,
c3,
)
if err != nil {
log.Fatal(err)
}
Expand Down