// LookupByAddress performs a federated lookup following to the stellar // federation protocol using the "name" type request. The provided address is // used to resolve what server the request should be made against. NOTE: the // "name" type is a legacy holdover from the legacy stellar network's federation // protocol. It is unfortunate. func (c *Client) LookupByAddress(addy string) (*NameResponse, error) { _, domain, err := address.Split(addy) if err != nil { return nil, errors.Wrap(err, "parse address failed") } fserv, err := c.getFederationServer(domain) if err != nil { return nil, errors.Wrap(err, "lookup federation server failed") } url := c.url(fserv, "name", addy) var resp NameResponse err = c.getJSON(url, &resp) if err != nil { return nil, errors.Wrap(err, "get federation failed") } if resp.MemoType != "" && resp.Memo == "" { return nil, errors.New("Invalid federation response (memo)") } return &resp, nil }
// LookupByAccountID performs a federated lookup following to the stellar // federation protocol using the "id" type request. The provided strkey-encoded // account id is used to resolve what server the request should be made against. func (c *Client) LookupByAccountID(aid string) (*IDResponse, error) { domain, err := c.Horizon.HomeDomainForAccount(aid) if err != nil { return nil, errors.Wrap(err, "get homedomain failed") } if domain == "" { return nil, errors.New("homedomain not set") } fserv, err := c.getFederationServer(domain) if err != nil { return nil, errors.Wrap(err, "lookup federation server failed") } url := c.url(fserv, "id", aid) var resp IDResponse err = c.getJSON(url, &resp) if err != nil { return nil, errors.Wrap(err, "get federation failed") } return &resp, nil }
func (pl *PaymentListener) postForm( url string, form url.Values, ) (*http.Response, error) { strbody := form.Encode() req, err := http.NewRequest("POST", url, strings.NewReader(strbody)) if err != nil { return nil, errors.Wrap(err, "configure http request failed") } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if pl.config.MACKey != "" { rawMAC, err := pl.getMAC(pl.config.MACKey, []byte(strbody)) if err != nil { return nil, errors.Wrap(err, "getMAC failed") } encMAC := base64.StdEncoding.EncodeToString(rawMAC) req.Header.Set("X_PAYLOAD_MAC", encMAC) } resp, err := pl.client.Do(req) if err != nil { return nil, errors.Wrap(err, "http request errored") } return resp, nil }
func initDriver(cfg Config) (federation.Driver, error) { var dialect string switch cfg.Database.Type { case "mysql": dialect = "mysql" case "postgres": dialect = "postgres" case "sqlite3": dialect = "sqlite3" default: return nil, errors.Errorf("Invalid db type: %s", cfg.Database.Type) } repo, err := db.Open(dialect, cfg.Database.URL) if err != nil { return nil, errors.Wrap(err, "db open failed") } sqld := federation.SQLDriver{ DB: repo.DB.DB, // unwrap the repo to the bare *sql.DB instance, LookupRecordQuery: cfg.Queries.Federation, } if cfg.Queries.ReverseFederation == "" { return &sqld, nil } rsqld := federation.ReverseSQLDriver{ SQLDriver: sqld, LookupReverseRecordQuery: cfg.Queries.ReverseFederation, } return &rsqld, nil }
// Run starts an http server using the provided config struct. // // This method configures the process to listen for termination signals (SIGINT // and SIGTERM) to trigger a graceful shutdown by way of the graceful package // (https://github.com/tylerb/graceful). func Run(conf Config) { srv := setup(conf) http2.ConfigureServer(srv.Server, nil) if conf.OnStarting != nil { conf.OnStarting() } var err error if conf.TLSCert != "" { err = srv.ListenAndServeTLS(conf.TLSCert, conf.TLSKey) } else { err = srv.ListenAndServe() } if err != nil { log.Error(errors.Wrap(err, "failed to start server")) os.Exit(1) } if conf.OnStopped != nil { conf.OnStopped() } os.Exit(0) }
// package searches the `tools` and `services` packages of this repo to find // the source directory. This is used within the script to find the README and // other files that should be packaged with the binary. func packageName(binName string) string { targets := []string{ filepath.Join("services", binName), filepath.Join("tools", binName), } var result string // Note: we do not short circuit this search when we find a valid result so // that we can panic when multiple results are found. The children of // /services and /tools should not have name overlap. for _, t := range targets { _, err := os.Stat(t) if os.IsNotExist(err) { continue } if err != nil { panic(errors.Wrap(err, "stat failed")) } if result != "" { panic("sourceDir() found multiple results!") } result = t } return result }
// GetStellarTomlByAddress returns stellar.toml file of a domain fetched from a // given address func (c *Client) GetStellarTomlByAddress(addy string) (*Response, error) { _, domain, err := address.Split(addy) if err != nil { return nil, errors.Wrap(err, "parse address failed") } return c.GetStellarToml(domain) }
// build converts the provided sql builder `b` into the sql and args to execute // against the raw database connections. func (r *Repo) build(b sq.Sqlizer) (sql string, args []interface{}, err error) { sql, args, err = b.ToSql() if err != nil { err = errors.Wrap(err, "to-sql failed") } return }
// Open the postgres database at `url` and returns a new *Repo using it. func Open(dialect, url string) (*Repo, error) { db, err := sqlx.Connect(dialect, url) if err != nil { return nil, errors.Wrap(err, "connect failed") } return &Repo{DB: db}, nil }
// pushdir is a utility function to temporarily change directories. It returns // a func that can be called to restore the current working directory to the // state it was in when first calling pushdir. func pushdir(dir string) func() { cwd, err := os.Getwd() if err != nil { panic(errors.Wrap(err, "getwd failed")) } err = os.Chdir(dir) if err != nil { panic(errors.Wrap(err, "chdir failed")) } return func() { err := os.Chdir(cwd) if err != nil { panic(errors.Wrap(err, "revert dir failed")) } } }
// getJSON populates `dest` with the contents at `url`, provided the request // succeeds and the json can be successfully decoded. func (c *Client) getJSON(url string, dest interface{}) error { hresp, err := c.HTTP.Get(url) if err != nil { return errors.Wrap(err, "http get errored") } defer hresp.Body.Close() if !(hresp.StatusCode >= 200 && hresp.StatusCode < 300) { return errors.Errorf("http get failed with (%d) status code", hresp.StatusCode) } err = json.NewDecoder(hresp.Body).Decode(dest) if err != nil { return errors.Wrap(err, "json decode errored") } return nil }
func (pl *PaymentListener) getMAC(key string, raw []byte) ([]byte, error) { rawkey, err := strkey.Decode(strkey.VersionByteSeed, pl.config.MACKey) if err != nil { return nil, errors.Wrap(err, "invalid MAC key") } macer := hmac.New(sha256.New, rawkey) macer.Write(raw) return macer.Sum(nil), nil }
// HashTransaction derives the network specific hash for the provided // transaction using the network identified by the supplied passphrase. The // resulting hash is the value that can be signed by stellar secret key to // authorize the transaction identified by the hash to stellar validators. func HashTransaction(tx *xdr.Transaction, passphrase string) ([32]byte, error) { var txBytes bytes.Buffer _, err := fmt.Fprintf(&txBytes, "%s", ID(passphrase)) if err != nil { return [32]byte{}, errors.Wrap(err, "fprint network id failed") } _, err = xdr.Marshal(&txBytes, xdr.EnvelopeTypeEnvelopeTypeTx) if err != nil { return [32]byte{}, errors.Wrap(err, "marshal type failed") } _, err = xdr.Marshal(&txBytes, tx) if err != nil { return [32]byte{}, errors.Wrap(err, "marshal tx failed") } return hash.Hash(txBytes.Bytes()), nil }
// Envelope extracts the transaction envelope that triggered this error from the // extra fields. func (herr *Error) Envelope() (*xdr.TransactionEnvelope, error) { raw, ok := herr.Problem.Extras["envelope_xdr"] if !ok { return nil, ErrEnvelopeNotPopulated } var b64 string var result xdr.TransactionEnvelope err := json.Unmarshal(raw, &b64) if err != nil { return nil, errors.Wrap(err, "json decode failed") } err = xdr.SafeUnmarshalBase64(b64, &result) if err != nil { return nil, errors.Wrap(err, "xdr decode failed") } return &result, nil }
// BuildTime returns the time that the binary of the current process was built. // Our build script populates the `buildTime` var used to provide this result. func BuildTime() (time.Time, error) { if buildTime == "" { return time.Time{}, ErrNoBuildTime } t, err := time.Parse(time.RFC3339, buildTime) if err != nil { return time.Time{}, errors.Wrap(err, "parse failed") } return t, nil }
// randomName returns a new psuedo-random name that is sufficient for naming a // test database. In the event that reading from the source of randomness // fails, a panic will occur. func randomName() string { raw := make([]byte, 6) _, err := rand.Read(raw) if err != nil { err = errors.Wrap(err, "read from rand failed") panic(err) } enc := hex.EncodeToString(raw) return fmt.Sprintf("test_%s", enc) }
// GetStellarToml returns stellar.toml file for a given domain func (c *Client) GetStellarToml(domain string) (resp *Response, err error) { var hresp *http.Response hresp, err = c.HTTP.Get(c.url(domain)) if err != nil { err = errors.Wrap(err, "http request errored") return } defer hresp.Body.Close() if !(hresp.StatusCode >= 200 && hresp.StatusCode < 300) { err = errors.New("http request failed with non-200 status code") return } _, err = toml.DecodeReader(hresp.Body, &resp) if err != nil { err = errors.Wrap(err, "toml decode failed") return } return }
// Begin binds this repo to a new transaction. func (r *Repo) Begin() error { if r.tx != nil { return errors.New("already in transaction") } tx, err := r.DB.Beginx() if err != nil { return errors.Wrap(err, "beginx failed") } r.logBegin() r.tx = tx return nil }
// Postgres provisions a new, blank database with a random name on the localhost // of the running process. It assumes that you have postgres running on the // default port, have the command line postgres tools installed, and that the // current user has access to the server. It panics on the event of a failure. func Postgres() *DB { var result DB name := randomName() result.Dialect = "postgres" result.DSN = fmt.Sprintf("postgres://localhost/%s?sslmode=disable", name) // create the db err := exec.Command("createdb", name).Run() if err != nil { err = errors.Wrap(err, "createdb failed") panic(err) } result.closer = func() { err := exec.Command("dropdb", name).Run() if err != nil { err = errors.Wrap(err, "dropdb failed") panic(err) } } return &result }
// Mysql provisions a new, blank database with a random name on the localhost of // the running process. It assumes that you have mysql running and that the // root user has access with no password. It panics on // the event of a failure. func Mysql() *DB { var result DB name := randomName() result.Dialect = "mysql" result.DSN = fmt.Sprintf("root@/%s", name) // create the db err := exec.Command("mysql", "-e", fmt.Sprintf("CREATE DATABASE %s;", name)).Run() if err != nil { err = errors.Wrap(err, "createdb failed") panic(err) } result.closer = func() { err := exec.Command("mysql", "-e", fmt.Sprintf("DROP DATABASE %s;", name)).Run() if err != nil { err = errors.Wrap(err, "dropdb failed") panic(err) } } return &result }
// LookupRecord implements `Driver` by performing `drv.LookupRecordQuery` // against `drv.DB` using the provided parameters func (drv *SQLDriver) LookupRecord(name, domain string) (*Record, error) { drv.initDB() var result Record err := drv.db.GetRaw(&result, drv.LookupRecordQuery, name, domain) if drv.db.NoRows(err) { return nil, nil } else if err != nil { return nil, errors.Wrap(err, "db get") } return &result, nil }
// LookupReverseRecord implements `ReverseDriver` by performing // `drv.LookupReverseRecordQuery` against `drv.DB` using the provided parameter func (drv *ReverseSQLDriver) LookupReverseRecord( accountid string, ) (*ReverseRecord, error) { drv.initDB() var result ReverseRecord err := drv.db.GetRaw(&result, drv.LookupReverseRecordQuery, accountid) if drv.db.NoRows(err) { return nil, nil } else if err != nil { return nil, errors.Wrap(err, "db get") } return &result, nil }
// ExecRaw runs `query` with `args` func (r *Repo) ExecRaw(query string, args ...interface{}) (sql.Result, error) { query = r.conn().Rebind(query) start := time.Now() result, err := r.conn().Exec(query, args...) r.log("exec", start, query, args) if err == nil { return result, nil } if r.NoRows(err) { return nil, err } return nil, errors.Wrap(err, "exec failed") }
// QueryRaw runs `query` with `args` func (r *Repo) QueryRaw(query string, args ...interface{}) (*sqlx.Rows, error) { query = r.conn().Rebind(query) start := time.Now() result, err := r.conn().Queryx(query, args...) r.log("query", start, query, args) if err == nil { return result, nil } if r.NoRows(err) { return nil, err } return nil, errors.Wrap(err, "query failed") }
// GetRaw runs `query` with `args`, setting the first result found on // `dest`, if any. func (r *Repo) GetRaw(dest interface{}, query string, args ...interface{}) error { query = r.conn().Rebind(query) start := time.Now() err := r.conn().Get(dest, query, args...) r.log("get", start, query, args) if err == nil { return nil } if r.NoRows(err) { return err } return errors.Wrap(err, "get failed") }
func (c *Client) getFederationServer(domain string) (string, error) { stoml, err := c.StellarTOML.GetStellarToml(domain) if err != nil { return "", errors.Wrap(err, "get stellar.toml failed") } if stoml.FederationServer == "" { return "", errors.New("stellar.toml is missing federation server info") } if !c.AllowHTTP && !strings.HasPrefix(stoml.FederationServer, "https://") { return "", errors.New("non-https federation server disallowed") } return stoml.FederationServer, nil }
// Read takes the TOML configuration file at `path`, parses it into `dest` and // then uses github.com/asaskevich/govalidator to validate the struct. func Read(path string, dest interface{}) error { _, err := toml.DecodeFile(path, dest) if err != nil { return errors.Wrap(err, "decode-file failed") } valid, err := govalidator.ValidateStruct(dest) if valid { return nil } fields := govalidator.ErrorsByField(err) return &InvalidConfigError{ InvalidFields: fields, } }
// ResultCodes extracts a result code summary from the error, if possible. func (herr *Error) ResultCodes() (*TransactionResultCodes, error) { if herr.Problem.Type != "transaction_failed" { return nil, ErrTransactionNotFailed } raw, ok := herr.Problem.Extras["result_codes"] if !ok { return nil, ErrResultCodesNotPopulated } var result TransactionResultCodes err := json.Unmarshal(raw, &result) if err != nil { return nil, errors.Wrap(err, "json decode failed") } return &result, nil }
// SelectRaw runs `query` with `args`, setting the results found on `dest`. func (r *Repo) SelectRaw( dest interface{}, query string, args ...interface{}, ) error { r.clearSliceIfPossible(dest) query = r.conn().Rebind(query) start := time.Now() err := r.conn().Select(dest, query, args...) r.log("select", start, query, args) if err == nil { return nil } if r.NoRows(err) { return err } return errors.Wrap(err, "select failed") }