Skip to content

Commit

Permalink
Merge branch 'main' into bug/database_locked and update database struct
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmenendez committed Sep 6, 2023
2 parents 887add1 + 8d9fd54 commit 1b6199e
Show file tree
Hide file tree
Showing 34 changed files with 321 additions and 244 deletions.
3 changes: 1 addition & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# A web3 endpoint provider
# WEB3_PROVIDER="https://mainnet.infura.io/v3/..."
WEB3_PROVIDER="https://web3.dappnode.net"
WEB3_PROVIDERS=https://rpc-endoint.example1.com,https://rpc-endoint.example2.com

# Internal port for the service (80 and 443 are used by traefik)
PORT=7788
Expand Down
27 changes: 25 additions & 2 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
# API endpoints

Endpoints:
- [API info](#api-info)
- [Tokens](#tokens)
- [Strategies](#strategies)
- [Censuses](#censuses)

## API Info

### GET `/info`

Show information about the API service.

- 📥 response:

```json
{
"chainIDs": [1, 5]
}
```

- ⚠️ errors:

| HTTP Status | Message | Internal error |
|:---:|:---|:---:|
| 500 | `error encoding API info` | 5023 |

## Tokens

### GET `/token`
Expand Down Expand Up @@ -279,7 +300,8 @@ Request the creation of a new census with the strategy provided for the `blockNu
```json
{
"strategyId": 1,
"blockNumber": 123456
"blockNumber": 123456,
"anonymous": false
}
```

Expand Down Expand Up @@ -312,7 +334,8 @@ Returns the information of the snapshots related to the provided ID.
"uri": "ipfs://Qma....",
"size": 1000,
"weight": "200000000000000000000",
"chainId": 1
"chainId": 1,
"anonymous": true
}
```

Expand Down
86 changes: 32 additions & 54 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,42 @@
package api

import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"encoding/json"

"github.com/ethereum/go-ethereum/ethclient"
"github.com/vocdoni/census3/census"
queries "github.com/vocdoni/census3/db/sqlc"
"github.com/vocdoni/census3/db"
"go.vocdoni.io/dvote/httprouter"
api "go.vocdoni.io/dvote/httprouter/apirest"
"go.vocdoni.io/dvote/log"
)

type Census3APIConf struct {
Hostname string
Port int
DataDir string
Web3URI string
GroupKey string
Hostname string
Port int
DataDir string
GroupKey string
Web3Providers map[int64]string
}

type census3API struct {
conf Census3APIConf
web3 string
db *sql.DB
sqlc *queries.Queries
db *db.DB
endpoint *api.API
censusDB *census.CensusDB
w3p map[int64]string
}

func Init(db *sql.DB, q *queries.Queries, conf Census3APIConf) error {
func Init(db *db.DB, conf Census3APIConf) error {
newAPI := &census3API{
conf: conf,
web3: conf.Web3URI,
db: db,
sqlc: q,
w3p: conf.Web3Providers,
}
// get the current chainID
chainID, err := newAPI.setupChainID()
if err != nil {
log.Fatal(err)
}
log.Infow("starting API", "chainID", chainID, "web3", conf.Web3URI)
log.Infow("starting API", "chainID-web3Providers", conf.Web3Providers)

// create a new http router with the hostname and port provided in the conf
var err error
r := httprouter.HTTProuter{}
if err = r.Init(conf.Hostname, conf.Port); err != nil {
return err
Expand All @@ -60,6 +50,9 @@ func Init(db *sql.DB, q *queries.Queries, conf Census3APIConf) error {
return err
}
// init handlers
if err := newAPI.initAPIHandlers(); err != nil {
return err
}
if err := newAPI.initTokenHandlers(); err != nil {
return err
}
Expand All @@ -76,38 +69,23 @@ func Init(db *sql.DB, q *queries.Queries, conf Census3APIConf) error {
return nil
}

// setup function gets the chainID from the web3 uri and checks if it is
// registered in the database. If it is registered, the function compares both
// values and panics if they are not the same. If it is not registered, the
// function stores it.
func (capi *census3API) setupChainID() (int64, error) {
web3client, err := ethclient.Dial(capi.web3)
if err != nil {
return -1, fmt.Errorf("error dialing to the web3 endpoint: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// get the chainID from the web3 endpoint
chainID, err := web3client.ChainID(ctx)
if err != nil {
return -1, fmt.Errorf("error getting the chainID from the web3 endpoint: %w", err)
func (capi *census3API) initAPIHandlers() error {
return capi.endpoint.RegisterMethod("/info", "GET",
api.MethodAccessTypePublic, capi.getAPIInfo)
}

func (capi *census3API) getAPIInfo(msg *api.APIdata, ctx *httprouter.HTTPContext) error {
chainIDs := []int64{}
for chainID := range capi.w3p {
chainIDs = append(chainIDs, chainID)
}
// get the current chainID from the database
currentChainID, err := capi.sqlc.ChainID(ctx)

info := map[string]any{"chainIDs": chainIDs}
res, err := json.Marshal(info)
if err != nil {
// if it not exists register the value received from the web3 endpoint
if errors.Is(err, sql.ErrNoRows) {
_, err := capi.sqlc.SetChainID(ctx, chainID.Int64())
if err != nil {
return -1, fmt.Errorf("error setting the chainID in the database: %w", err)
}
return chainID.Int64(), nil
}
return -1, fmt.Errorf("error getting chainID from the database: %w", err)
}
// compare both values
if currentChainID != chainID.Int64() {
return -1, fmt.Errorf("received chainID is not the same that registered one: %w", err)
log.Errorw(err, "error encoding api info")
return ErrEncodeAPIInfo
}
return currentChainID, nil

return ctx.Send(res, api.HTTPstatusOK)
}
21 changes: 9 additions & 12 deletions api/censuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (capi *census3API) getCensus(msg *api.APIdata, ctx *httprouter.HTTPContext)
internalCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// begin a transaction for group sql queries
tx, err := capi.db.BeginTx(internalCtx, nil)
tx, err := capi.db.RO.BeginTx(internalCtx, nil)
if err != nil {
return ErrCantGetCensus
}
Expand All @@ -49,26 +49,22 @@ func (capi *census3API) getCensus(msg *api.APIdata, ctx *httprouter.HTTPContext)
log.Errorw(err, "holders transaction rollback failed")
}
}()
qtx := capi.sqlc.WithTx(tx)
qtx := capi.db.QueriesRO.WithTx(tx)
currentCensus, err := qtx.CensusByID(internalCtx, int64(censusID))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return ErrNotFoundCensus
}
return ErrCantGetCensus
}
chainID, err := qtx.ChainID(internalCtx)
if err != nil {
return ErrCantGetCensus
}
res, err := json.Marshal(GetCensusResponse{
CensusID: uint64(censusID),
StrategyID: uint64(currentCensus.StrategyID),
MerkleRoot: common.Bytes2Hex(currentCensus.MerkleRoot),
URI: "ipfs://" + currentCensus.Uri.String,
Size: int32(currentCensus.Size),
Weight: new(big.Int).SetBytes(currentCensus.Weight).String(),
ChainID: uint64(chainID),
Anonymous: currentCensus.CensusType == int64(census.AnonymousCensusType),
})
if err != nil {
return ErrEncodeCensus
Expand All @@ -94,16 +90,16 @@ func (capi *census3API) createAndPublishCensus(msg *api.APIdata, ctx *httprouter
defer cancel()

// begin a transaction for group sql queries
tx, err := capi.db.BeginTx(internalCtx, nil)
tx, err := capi.db.RW.BeginTx(internalCtx, nil)
if err != nil {
return ErrCantCreateCensus
}
defer func() {
if err := tx.Rollback(); err != nil {
if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) {
log.Errorw(err, "holders transaction rollback failed")
}
}()
qtx := capi.sqlc.WithTx(tx)
qtx := capi.db.QueriesRW.WithTx(tx)

strategyTokens, err := qtx.TokensByStrategyID(internalCtx, int64(req.StrategyID))
if err != nil {
Expand Down Expand Up @@ -146,7 +142,7 @@ func (capi *census3API) createAndPublishCensus(msg *api.APIdata, ctx *httprouter
return ErrCantCreateCensus
}
// create a census tree and publish on IPFS
def := census.DefaultCensusDefinition(int(lastCensusID+1), int(req.StrategyID), strategyHolders)
def := census.NewCensusDefinition(int(lastCensusID+1), int(req.StrategyID), strategyHolders, req.Anonymous)
newCensus, err := capi.censusDB.CreateAndPublish(def)
if err != nil {
log.Errorw(err, "error creating or publishing the census")
Expand Down Expand Up @@ -183,6 +179,7 @@ func (capi *census3API) createAndPublishCensus(msg *api.APIdata, ctx *httprouter
Uri: *sqlURI,
Size: int64(len(strategyHolders)),
Weight: censusWeight.Bytes(),
CensusType: int64(def.Type),
})
if err != nil {
log.Errorw(err, "error saving the census on the database")
Expand Down Expand Up @@ -213,7 +210,7 @@ func (capi *census3API) getStrategyCensuses(msg *api.APIdata, ctx *httprouter.HT
// get censuses by this strategy ID
internalCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
rows, err := capi.sqlc.CensusByStrategyID(internalCtx, int64(strategyID))
rows, err := capi.db.QueriesRO.CensusByStrategyID(internalCtx, int64(strategyID))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return ErrNotFoundCensus
Expand Down
4 changes: 2 additions & 2 deletions api/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (capi *census3API) getTokenHolders(msg *api.APIdata, ctx *httprouter.HTTPCo

// get token holders from the database
addr := common.HexToAddress(ctx.URLParam("address"))
dbHolders, err := capi.sqlc.TokenHoldersByTokenID(ctx2, addr.Bytes())
dbHolders, err := capi.db.QueriesRO.TokenHoldersByTokenID(ctx2, addr.Bytes())
if err != nil {
// if database does not contain any token holder for this token, return
// no content, else return generic error.
Expand Down Expand Up @@ -77,7 +77,7 @@ func (capi *census3API) countHolders(msg *api.APIdata, ctx *httprouter.HTTPConte
defer cancel()

addr := common.HexToAddress(ctx.URLParam("address"))
numberOfHolders, err := capi.sqlc.CountTokenHoldersByTokenID(ctx2, addr.Bytes())
numberOfHolders, err := capi.db.QueriesRO.CountTokenHoldersByTokenID(ctx2, addr.Bytes())
if err != nil {
if errors.Is(sql.ErrNoRows, err) {
log.Errorf("no holders found for address %s: %s", addr, err.Error())
Expand Down
10 changes: 10 additions & 0 deletions api/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ var (
HTTPstatus: http.StatusConflict,
Err: fmt.Errorf("token already created"),
}
ErrChainIDNotSupported = apirest.APIerror{
Code: 4013,
HTTPstatus: apirest.HTTPstatusBadRequest,
Err: fmt.Errorf("chain ID provided not supported"),
}
ErrCantCreateToken = apirest.APIerror{
Code: 5000,
HTTPstatus: apirest.HTTPstatusInternalErr,
Expand Down Expand Up @@ -168,4 +173,9 @@ var (
HTTPstatus: apirest.HTTPstatusInternalErr,
Err: fmt.Errorf("error getting last block number from web3 endpoint"),
}
ErrEncodeAPIInfo = apirest.APIerror{
Code: 5023,
HTTPstatus: apirest.HTTPstatusInternalErr,
Err: fmt.Errorf("error encoding API info"),
}
)
12 changes: 6 additions & 6 deletions api/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ func (capi *census3API) initStrategiesHandlers() error {
func (capi *census3API) createDummyStrategy(tokenID []byte) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
res, err := capi.sqlc.CreateStategy(ctx, "test")
res, err := capi.db.QueriesRW.CreateStategy(ctx, "test")
if err != nil {
return err
}
strategyID, err := res.LastInsertId()
if err != nil {
return err
}
_, err = capi.sqlc.CreateStrategyToken(ctx, queries.CreateStrategyTokenParams{
_, err = capi.db.QueriesRW.CreateStrategyToken(ctx, queries.CreateStrategyTokenParams{
StrategyID: strategyID,
TokenID: tokenID,
MinBalance: big.NewInt(0).Bytes(),
Expand All @@ -62,7 +62,7 @@ func (capi *census3API) getStrategies(msg *api.APIdata, ctx *httprouter.HTTPCont
defer cancel()
// TODO: Support for pagination
// get strategies from the database
rows, err := capi.sqlc.ListStrategies(internalCtx)
rows, err := capi.db.QueriesRO.ListStrategies(internalCtx)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return ErrNoStrategies
Expand Down Expand Up @@ -100,7 +100,7 @@ func (capi *census3API) getStrategy(msg *api.APIdata, ctx *httprouter.HTTPContex
// get strategy from the database
internalCtx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
strategyData, err := capi.sqlc.StrategyByID(internalCtx, int64(strategyID))
strategyData, err := capi.db.QueriesRO.StrategyByID(internalCtx, int64(strategyID))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return ErrNotFoundStrategy
Expand All @@ -115,7 +115,7 @@ func (capi *census3API) getStrategy(msg *api.APIdata, ctx *httprouter.HTTPContex
Tokens: []GetStrategyToken{},
}
// get information of the strategy related tokens
tokensData, err := capi.sqlc.TokensByStrategyID(internalCtx, strategyData.ID)
tokensData, err := capi.db.QueriesRO.TokensByStrategyID(internalCtx, strategyData.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
log.Errorw(ErrCantGetTokens, err.Error())
return ErrCantGetTokens
Expand Down Expand Up @@ -147,7 +147,7 @@ func (capi *census3API) getTokenStrategies(msg *api.APIdata, ctx *httprouter.HTT
internalCtx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// get strategies associated to the token provided
rows, err := capi.sqlc.StrategiesByTokenID(internalCtx, common.HexToAddress(tokenID).Bytes())
rows, err := capi.db.QueriesRO.StrategiesByTokenID(internalCtx, common.HexToAddress(tokenID).Bytes())
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return ErrNoStrategies
Expand Down
Loading

0 comments on commit 1b6199e

Please sign in to comment.