Skip to content

Commit

Permalink
add country code
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Oct 29, 2024
1 parent 303af61 commit 1a3835f
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 6 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[![GoDoc](https://godoc.org/go.sia.tech/explored?status.svg)](https://godoc.org/go.sia.tech/explored)

`explored` is an explorer for Sia.

## Required Disclosure

`explored` uses the IP2Location LITE database for <a href="https://lite.ip2location.com">IP geolocation</a>.
5 changes: 3 additions & 2 deletions explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,9 @@ type HostScan struct {

// Host represents a host and the information gathered from scanning it.
type Host struct {
PublicKey types.PublicKey `json:"publicKey"`
NetAddress string `json:"netAddress"`
PublicKey types.PublicKey `json:"publicKey"`
NetAddress string `json:"netAddress"`
CountryCode string `json:"countryCode"`

KnownSince time.Time `json:"knownSince"`
LastScan time.Time `json:"lastScan"`
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
)

require (
github.com/ip2location/ip2location-go v8.3.0+incompatible // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/ip2location/ip2location-go v8.3.0+incompatible h1:QwUE+FlSbo6bjOWZpv2Grb57vJhWYFNPyBj2KCvfWaM=
github.com/ip2location/ip2location-go v8.3.0+incompatible/go.mod h1:3JUY1TBjTx1GdA7oRT7Zeqfc0bg3lMMuU5lXmzdpuME=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
Binary file added internal/geoip/IP2LOCATION-LITE-DB1.BIN
Binary file not shown.
72 changes: 72 additions & 0 deletions internal/geoip/geoip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package geoip

import (
_ "embed"
"errors"
"net"
"os"

"github.com/ip2location/ip2location-go"
)

//go:embed IP2LOCATION-LITE-DB1.BIN
var ip2LocationDB []byte

// A Locator maps IP addresses to their location.
type Locator interface {
// Close closes the Locator.
Close() error
// CountryCode maps IP addresses to ISO 3166-1 A-2 country codes.
CountryCode(ip *net.IPAddr) (string, error)
}

type ip2Location struct {
path string
db *ip2location.DB
}

// Close implements Locator.
func (ip *ip2Location) Close() error {
ip.db.Close()
return os.Remove(ip.path)
}

// CountryCode implements Locator.
func (ip *ip2Location) CountryCode(addr *net.IPAddr) (string, error) {
if ip == nil {
return "", errors.New("nil IP")
}

loc, err := ip.db.Get_country_short(addr.String())
if err != nil {
return "", err
}
return loc.Country_short, nil
}

// NewIP2LocationLocator returns a Locator that uses an underlying IP2Location
// database. If no path is provided, a default embedded LITE database is used.
func NewIP2LocationLocator(path string) (Locator, error) {
// Unfortuantely, ip2location.OpenDB only accepts a file path. So we need

Check failure on line 50 in internal/geoip/geoip.go

View workflow job for this annotation

GitHub Actions / test / test (1.23, macos-latest)

`Unfortuantely` is a misspelling of `Unfortunately` (misspell)
// to write the embedded file to a temporary file on disk, and use that
// instead.
if path == "" {
f, err := os.CreateTemp("", "geoip")
if err != nil {
return nil, err
} else if _, err := f.Write(ip2LocationDB); err != nil {
return nil, err
} else if err := f.Sync(); err != nil {
return nil, err
} else if err := f.Close(); err != nil {
return nil, err
}
path = f.Name()
}

db, err := ip2location.OpenDB(path)
if err != nil {
return nil, err
}
return &ip2Location{path: path, db: db}, nil
}
21 changes: 19 additions & 2 deletions persist/sqlite/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqlite
import (
"database/sql"
"fmt"
"net"
"time"

"go.sia.tech/core/types"
Expand Down Expand Up @@ -100,8 +101,8 @@ WHERE ev.event_id = ?`, eventID).Scan(decode(&m.SiacoinOutput.StateElement.ID),
}

// Hosts returns the hosts with the given public keys.
func (s *Store) Hosts(pks []types.PublicKey) (result []explorer.Host, err error) {
err = s.transaction(func(tx *txn) error {
func (st *Store) Hosts(pks []types.PublicKey) (result []explorer.Host, err error) {
err = st.transaction(func(tx *txn) error {
var encoded []any
for _, pk := range pks {
encoded = append(encoded, encode(pk))
Expand All @@ -119,6 +120,22 @@ func (s *Store) Hosts(pks []types.PublicKey) (result []explorer.Host, err error)
if err := rows.Scan(decode(&host.PublicKey), &host.NetAddress, decode(&host.KnownSince), decode(&host.LastScan), &host.LastScanSuccessful, decode(&host.LastAnnouncement), &host.TotalScans, &host.SuccessfulInteractions, &host.FailedInteractions, &s.AcceptingContracts, decode(&s.MaxDownloadBatchSize), decode(&s.MaxDuration), decode(&s.MaxReviseBatchSize), &s.NetAddress, decode(&s.RemainingStorage), decode(&s.SectorSize), decode(&s.TotalStorage), decode(&s.Address), decode(&s.WindowSize), decode(&s.Collateral), decode(&s.MaxCollateral), decode(&s.BaseRPCPrice), decode(&s.ContractPrice), decode(&s.DownloadBandwidthPrice), decode(&s.SectorAccessPrice), decode(&s.StoragePrice), decode(&s.UploadBandwidthPrice), &s.EphemeralAccountExpiry, decode(&s.MaxEphemeralAccountBalance), decode(&s.RevisionNumber), &s.Version, &s.Release, &s.SiaMuxPort, decode(&p.UID), &p.Validity, decode(&p.HostBlockHeight), decode(&p.UpdatePriceTableCost), decode(&p.AccountBalanceCost), decode(&p.FundAccountCost), decode(&p.LatestRevisionCost), decode(&p.SubscriptionMemoryCost), decode(&p.SubscriptionNotificationCost), decode(&p.InitBaseCost), decode(&p.MemoryTimeCost), decode(&p.DownloadBandwidthCost), decode(&p.UploadBandwidthCost), decode(&p.DropSectorsBaseCost), decode(&p.DropSectorsUnitCost), decode(&p.HasSectorBaseCost), decode(&p.ReadBaseCost), decode(&p.ReadLengthCost), decode(&p.RenewContractCost), decode(&p.RevisionBaseCost), decode(&p.SwapSectorBaseCost), decode(&p.WriteBaseCost), decode(&p.WriteLengthCost), decode(&p.WriteStoreCost), decode(&p.TxnFeeMinRecommended), decode(&p.TxnFeeMaxRecommended), decode(&p.ContractPrice), decode(&p.CollateralCost), decode(&p.MaxCollateral), decode(&p.MaxDuration), decode(&p.WindowSize), decode(&p.RegistryEntriesLeft), decode(&p.RegistryEntriesTotal)); err != nil {
return err
}

// We should fill in country code information if we can but still
// return the host even if we are unable to.
hostname, _, err := net.SplitHostPort(host.NetAddress)
// if we can parse the net address
if err == nil {
resolved, err := net.ResolveIPAddr("ip", hostname)
// if we can resolve the address
if err == nil {
countryCode, err := st.locator.CountryCode(resolved)
if err == nil {
host.CountryCode = countryCode
}
}
}

result = append(result, host)
}
return nil
Expand Down
1 change: 1 addition & 0 deletions persist/sqlite/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func TestScan(t *testing.T) {
host2 := dbHosts[1]
testutil.Equal(t, "host2.NetAddress", hosts[1].NetAddress, host2.NetAddress)
testutil.Equal(t, "host2.PublicKey", hosts[1].PublicKey, host2.PublicKey)
testutil.Equal(t, "host2.CountryCode", "CA", host2.CountryCode)
testutil.Equal(t, "host2.TotalScans", 1, host2.TotalScans)
testutil.Equal(t, "host2.SuccessfulInteractions", 1, host2.SuccessfulInteractions)
testutil.Equal(t, "host2.FailedInteractions", 0, host2.FailedInteractions)
Expand Down
16 changes: 14 additions & 2 deletions persist/sqlite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sqlite

import (
"database/sql"
_ "embed"

Check failure on line 5 in persist/sqlite/store.go

View workflow job for this annotation

GitHub Actions / test / test (1.23, macos-latest)

blank-imports: a blank import should be only in a main or test package, or have a comment justifying it (revive)
"encoding/hex"
"errors"
"fmt"
Expand All @@ -10,6 +11,7 @@ import (
"time"

"github.com/mattn/go-sqlite3"
"go.sia.tech/explored/internal/geoip"
"go.uber.org/zap"
"lukechampine.com/frand"
)
Expand All @@ -19,6 +21,8 @@ type (
Store struct {
db *sql.DB
log *zap.Logger

locator geoip.Locator
}
)

Expand Down Expand Up @@ -53,6 +57,7 @@ func (s *Store) transaction(fn func(*txn) error) error {

// Close closes the underlying database.
func (s *Store) Close() error {
s.locator.Close()
return s.db.Close()
}

Expand Down Expand Up @@ -103,9 +108,16 @@ func OpenDatabase(fp string, log *zap.Logger) (*Store, error) {
if err != nil {
return nil, err
}
// use default included ip2location database
locator, err := geoip.NewIP2LocationLocator("")
if err != nil {
return nil, err
}

store := &Store{
db: db,
log: log,
db: db,
log: log,
locator: locator,
}
if err := store.init(); err != nil {
return nil, fmt.Errorf("failed to initialize database: %w", err)
Expand Down

0 comments on commit 1a3835f

Please sign in to comment.