Skip to content

Commit

Permalink
add address/{address}/exists endpoint
Browse files Browse the repository at this point in the history
Provides access to the existsaddresses RPC and parses the result.
  • Loading branch information
buck54321 committed Mar 21, 2020
1 parent 996a84f commit ba839cf
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 21 deletions.
1 change: 1 addition & 0 deletions api/apirouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func NewAPIRouter(app *appContext, useRealIP, compressLarge bool) apiMux {
r.Route("/{address}", func(rd chi.Router) {
rd.Use(m.AddressPathCtx)
rd.Get("/totals", app.addressTotals)
rd.Get("/exists", app.addressExists)
rd.Get("/", app.getAddressTransactions)
rd.With(m.ChartGroupingCtx).Get("/types/{chartgrouping}", app.getAddressTxTypesData)
rd.With(m.ChartGroupingCtx).Get("/amountflow/{chartgrouping}", app.getAddressTxAmountFlowData)
Expand Down
48 changes: 45 additions & 3 deletions api/apiroutes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"context"
"database/sql"
"encoding/binary"
"encoding/csv"
"encoding/hex"
"encoding/json"
Expand Down Expand Up @@ -36,9 +37,15 @@ import (
appver "github.com/decred/dcrdata/v5/version"
)

// maxBlockRangeCount is the maximum number of blocks that can be requested at
// once.
const maxBlockRangeCount = 1000
const (
// maxBlockRangeCount is the maximum number of blocks that can be requested at
// once.
maxBlockRangeCount = 1000

// maxExistsAddrs is the highest number of addresses accepted to the
// address/{address}/exists request.
maxExistAddrs = 64
)

// DataSource specifies an interface for advanced data collection using the
// auxiliary DB (e.g. PostgreSQL).
Expand Down Expand Up @@ -1734,6 +1741,41 @@ func (c *appContext) addressTotals(w http.ResponseWriter, r *http.Request) {
writeJSON(w, totals, indent)
}

// addressExists provides access to the existsaddresses RPC call and parses the
// hexadecimal string into a list of bools. A maximum of 64 addresses can be
// provided. Duplicates are not filtered.
func (c *appContext) addressExists(w http.ResponseWriter, r *http.Request) {
indent, err := c.getIndentQuery(r)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}

addresses, err := m.GetAddressRawCtx(r, c.Params, maxExistAddrs)
if err != nil {
apiLog.Errorf("addressExists rejecting request: %v", err)
http.Error(w, "address parsing error", http.StatusBadRequest)
return
}
// GetAddressCtx throws an error if there would be no addresses.
strMask, err := c.nodeClient.ExistsAddresses(addresses)
if err != nil {
log.Warnf("existsaddress error: %v", err)
http.Error(w, http.StatusText(422), 422)
}
b, err := hex.DecodeString(strMask)
if err != nil {
log.Warnf("existsaddress error: %v", err)
http.Error(w, http.StatusText(422), 422)
}
mask := binary.LittleEndian.Uint64(append(b, make([]byte, 8-len(b))...))
exists := make([]bool, 0, len(addresses))
for n := range addresses {
exists = append(exists, (mask&(1<<uint8(n))) != 0)
}
writeJSON(w, exists, indent)
}

// Handler for address activity CSV file download.
// /download/address/io/{address}?cr=[true|false]
func (c *appContext) addressIoCsv(w http.ResponseWriter, r *http.Request) {
Expand Down
69 changes: 51 additions & 18 deletions middleware/apimiddleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,20 +389,13 @@ func GetBlockHashCtx(r *http.Request) (string, error) {
return hashStr, nil
}

// GetAddressCtx retrieves the CtxAddress data from the request context. If not
// set, the return value is an empty string. The CtxAddress string data may be a
// comma-separated list of addresses, subject to the provided maximum number of
// addresses allowed. Duplicate addresses are removed, but the limit is enforced
// prior to removal of duplicates.
// GetAddressCtx returns a slice of base-58 encoded addresses parsed from the
// {address} URL parameter. Duplicate addresses are removed. Multiple
// comma-delimited address can be specified.
func GetAddressCtx(r *http.Request, activeNetParams *chaincfg.Params, maxAddrs int) ([]string, error) {
addressStr, ok := r.Context().Value(CtxAddress).(string)
if !ok || len(addressStr) == 0 {
apiLog.Trace("address not set")
return nil, fmt.Errorf("address not set")
}
addressStrs := strings.Split(addressStr, ",")
if len(addressStrs) > maxAddrs {
return nil, fmt.Errorf("maximum of %d addresses allowed", maxAddrs)
addressStrs, err := addressStrings(r, maxAddrs)
if err != nil {
return nil, err
}

strInSlice := func(sl []string, s string) bool {
Expand All @@ -414,21 +407,61 @@ func GetAddressCtx(r *http.Request, activeNetParams *chaincfg.Params, maxAddrs i
return false
}

var addrStrs []string
// Allocate as if all addresses are unique.
addrStrs := make([]string, 0, len(addressStrs))
for _, addrStr := range addressStrs {
if strInSlice(addrStrs, addrStr) {
continue
}
addrStrs = append(addrStrs, addrStr)
}

for _, addrStr := range addressStrs {
_, err := dcrutil.DecodeAddress(addrStr, activeNetParams)
if err != nil {
return nil, fmt.Errorf("invalid address '%v' for this network: %v",
addrStr, err)
}
if strInSlice(addrStrs, addrStr) {
continue
}
addrStrs = append(addrStrs, addrStr)
}
return addrStrs, nil
}

// GetAddressRawCtx returns a slice of addresses parsed from the {address} URL
// parameter. Multiple comma-delimited address strings can be specified.
func GetAddressRawCtx(r *http.Request, activeNetParams *chaincfg.Params, maxAddrs int) ([]dcrutil.Address, error) {
addressStrs, err := addressStrings(r, maxAddrs)
if err != nil {
return nil, err
}
addresses := make([]dcrutil.Address, 0, len(addressStrs))
for _, addrStr := range addressStrs {
addr, err := dcrutil.DecodeAddress(addrStr, activeNetParams)
if err != nil {
return nil, fmt.Errorf("invalid address '%v' for this network: %v",
addrStr, err)
}
addresses = append(addresses, addr)
}
return addresses, nil
}

// addressStrings retrieves the CtxAddress data from the request context. If not
// set, an error is generated. The CtxAddress string data may be a
// comma-separated list of addresses, subject to the maxAddrs limit.
func addressStrings(r *http.Request, maxAddrs int) ([]string, error) {
addressStr, ok := r.Context().Value(CtxAddress).(string)
if !ok || len(addressStr) == 0 {
apiLog.Trace("address not set")
return nil, fmt.Errorf("address not set")
}
addressStrs := strings.Split(addressStr, ",")
if len(addressStrs) > maxAddrs {
return nil, fmt.Errorf("maximum of %d addresses allowed", maxAddrs)
}

return addressStrs, nil
}

// GetChartTypeCtx retrieves the ctxChart data from the request context.
// If not set, the return value is an empty string.
func GetChartTypeCtx(r *http.Request) string {
Expand Down

0 comments on commit ba839cf

Please sign in to comment.