bridges

package module
v0.0.0-...-aa6f132 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2020 License: GPL-3.0 Imports: 13 Imported by: 7

README

Build Status codecov Go Report Card

Bridges is a Chainlink adaptor framework, lowering the barrier of entry for anyone to create their own:

  • A tested hardened library that removes the need to build your own HTTP server, allowing you to just focus on adapter requirements.
  • Simple interface to allow you to build an adapter that confides to Chainlink schema.
  • Kept up to date with any changes, meaning no extra work for existing adapters to support new schema changes or features.
  • Supports running in serverless environments such as AWS Lambda & GCP functions with minimal effort.

Contents

  1. Code Examples
  2. Running in AWS Lambda
  3. Running in GCP Functions
  4. Example Implementations

Code Examples

  • CryptoCompare: Simplest example.
  • API Aggregator: Aggregates multiple endpoints using mean/median/mode.
  • Wolfram Alpha: Short answers API, non-JSON, uses string splitting.
  • Gas Station: Single answer response, no authentication.
  • Asset Price: A more complex example that aggregates crypto asset prices from multiple exchanges by weighted volume.

Running in Docker

After implementing your bridge, if you'd like to run it in Docker, you can reference the Dockerfiles in examples to then use as a template for your own Dockerfile.

Running in AWS Lambda

After you've completed implementing your bridge, you can then test it in AWS Lambda. To do so:

  1. Build the executable:
    GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o bridge
    
  2. Add the file to a ZIP archive:
    zip bridge.zip ./bridge
    
  3. Upload the the zip file into AWS and then use bridge as the handler.
  4. Set the LAMBDA environment variable to true in AWS for the adaptor to be compatible with Lambda.

Running in GCP Functions

Due to the difference in running Go within GCP Functions, it requires specific considerations for it be supported within your bridge:

  • Bridge implementation cannot be within the main package
  • An extra Handler function within your implementation:
    func Handler(w http.ResponseWriter, r *http.Request) {
        bridges.NewServer(&Example{}).Handler(w, r)
    }
    
  • A go.mod and go.sum within the sub-package that contains the Handler function

For an example implementation for GCP Functions, view the asset price adapter.

You can then use the gcloud CLI tool to deploy it, for example:

gcloud functions deploy bridge --runtime go111 --entry-point Handler --trigger-http

Example Implementations

Basic

Bridges works by providing a simple interface to confide to. The interface contains two functions, Run and Opts. The Run function is called on each HTTP request, Opts is called on start-up. Below is a very basic implementation that returns the value as passed in by Chainlink, set back as newValue in the response:

package main

import (
	"github.com/linkpoolio/bridges"
)

type MyAdapter struct{}

func (ma *MyAdapter) Run(h *bridge.Helper) (interface{}, error) {
	return map[string]string{"newValue": h.GetParam("value")}, nil
}

func (ma *MyAdapter) Opts() *bridge.Opts {
	return &bridge.Opts{
		Name:   "MyAdapter",
		Lambda: true,
	}
}

func main() {
	bridge.NewServer(&MyAdaptor{}).Start(8080)
}

Unauthenticated HTTP Calls

The bridges library provides a helper object that intends to make actions like performing HTTP calls simpler, removing the need to write extensive error handling or the need to have the knowledge of Go's in-built http libraries.

For example, this below implementation uses the HTTPCall function to make a simple unauthenticated call to ETH Gas Station:

package main

import (
	"github.com/linkpoolio/bridges"
)

type GasStation struct{}

func (gs *GasStation) Run(h *bridges.Helper) (interface{}, error) {
	obj := make(map[string]interface{})
	err := h.HTTPCall(
		http.MethodGet,
		"https://ethgasstation.info/json/ethgasAPI.json",
		&obj,
	)
	return obj, err
}

func (gs *GasStation) Opts() *bridges.Opts {
	return &bridges.Opts{
		Name:   "GasStation",
		Lambda: true,
	}
}

func main() {
	bridges.NewServer(&GasStation{}).Start(8080)
}

Authenticated HTTP Calls

Bridges also provides an interface to support authentication methods when making HTTP requests to external sources. By default, bridges supports authentication via HTTP headers or GET parameters.

Below is a modified version of the WolframAlpha adapter, showing authentication setting the appid header from the APP_ID environment variable:

package main

import (
	"errors"
    "fmt"
	"github.com/linkpoolio/bridges"
	"net/http"
	"os"
	"strings"
)

type WolframAlpha struct{}

func (cc *WolframAlpha) Run(h *bridges.Helper) (interface{}, error) {
	b, err := h.HTTPCallRawWithOpts(
		http.MethodGet,
		"https://api.wolframalpha.com/v1/result",
		bridges.CallOpts{
			Auth: bridges.NewAuth(bridges.AuthParam, "appid", os.Getenv("APP_ID")),
			Query: map[string]interface{}{
				"i": h.GetParam("query"),
			},
		},
	)
	return fmt.Sprint(b), err
}

func (cc *WolframAlpha) Opts() *bridges.Opts {
	return &bridges.Opts{
		Name:   "WolframAlpha",
		Lambda: true,
	}
}

func main() {
	bridges.NewServer(&WolframAlpha{}).Start(8080)
}

Contributing

We welcome all contributors, please raise any issues for any feature request, issue or suggestion you may have.

Documentation

Index

Constants

View Source
const (
	AuthParam  = "param"
	AuthHeader = "header"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Auth

type Auth interface {
	Authenticate(*http.Request)
}

Auth is the generic interface for how the client passes in their API key for authentication

func NewAuth

func NewAuth(authType string, key string, value string) Auth

NewAuth returns a pointer of an Auth implementation based on the type that was passed in

type Bridge

type Bridge interface {
	Opts() *Opts
	Run(h *Helper) (interface{}, error)
}

Bridge is the interface that can be implemented for custom Chainlink bridges

type CallOpts

type CallOpts struct {
	Auth             Auth                   `json:"-"`
	Query            map[string]interface{} `json:"query"`
	QueryPassthrough bool                   `json:"queryPassthrough"`
	Body             string                 `json:"body"`
	ExpectedCode     int                    `json:"expectedCode"`
}

CallOpts are the options given into a http call method

type Header struct {
	Key   string
	Value string
}

Header is the Auth implementation that requires a header to be set

func (*Header) Authenticate

func (p *Header) Authenticate(r *http.Request)

Authenticate takes the key and value given and sets it as a header

type Helper

type Helper struct {
	Data *JSON
	// contains filtered or unexported fields
}

Helper is given to the receiving bridge to use on run, giving the bridge the visibility to the input parameters from the node request and having simple functions for making http calls.

func NewHelper

func NewHelper(data *JSON) *Helper

func (*Helper) GetIntParam

func (h *Helper) GetIntParam(key string) int64

GetIntParam gets the int64 value of a key in the `data` JSON object that is given on request by the Chainlink node

func (*Helper) GetParam

func (h *Helper) GetParam(key string) string

GetIntParam gets the string value of a key in the `data` JSON object that is given on request by the Chainlink node

func (*Helper) HTTPCall

func (h *Helper) HTTPCall(method, url string, obj interface{}) error

HTTPCall performs a basic http call with no options

func (*Helper) HTTPCallRawWithOpts

func (h *Helper) HTTPCallRawWithOpts(method, url string, opts CallOpts) ([]byte, error)

HTTPCallRawWithOpts performs a HTTP call with any method and returns the raw byte body and any error Supported options:

  • Authentication methods for the API (query param, headers)
  • Query parameters via `opts.Query`
  • Passthrough through all json keys within the request `data` object via `opts.QueryPassthrough`
  • Pass in a body to send with the request via `opts.Body`
  • Send in post form kv via `opts.PostForm`
  • Return an error if the returning http status code is different to `opts.ExpectedCode`

func (*Helper) HTTPCallRawWithOptsWithContext

func (h *Helper) HTTPCallRawWithOptsWithContext(ctx context.Context, method, url string, opts CallOpts) ([]byte, error)

func (*Helper) HTTPCallWithContext

func (h *Helper) HTTPCallWithContext(ctx context.Context, method, url string, obj interface{}) error

func (*Helper) HTTPCallWithOpts

func (h *Helper) HTTPCallWithOpts(method, url string, obj interface{}, opts CallOpts) error

HTTPCallWithOpts mirrors HTTPCallRawWithOpts bar the returning byte body is unmarshalled into a given object pointer

func (*Helper) HTTPCallWithOptsWithContext

func (h *Helper) HTTPCallWithOptsWithContext(ctx context.Context, method, url string, obj interface{}, opts CallOpts) error

type JSON

type JSON struct {
	gjson.Result
}

Based on https://github.com/smartcontractkit/chainlink/blob/master/core/store/models/common.go#L128

func Parse

func Parse(b []byte) (*JSON, error)

ParseInterface parse a bytes slice into a JSON object.

func ParseInterface

func ParseInterface(obj interface{}) (*JSON, error)

ParseInterface attempts to coerce the input interface and parse it into a JSON object.

func (*JSON) MarshalJSON

func (j *JSON) MarshalJSON() ([]byte, error)

MarshalJSON returns the JSON data if it already exists, returns an empty JSON object as bytes if not.

func (*JSON) UnmarshalJSON

func (j *JSON) UnmarshalJSON(b []byte) error

UnmarshalJSON parses the JSON bytes and stores in the *JSON pointer.

type Opts

type Opts struct {
	Name   string `json:"name"`
	Path   string `json:"path"`
	Lambda bool   `json:"Lambda"`
}

Opts is the options for each bridge

type Param

type Param struct {
	Key   string
	Value string
}

Query is the Auth implementation that requires GET param set

func (*Param) Authenticate

func (p *Param) Authenticate(r *http.Request)

Authenticate takes the `apikey` in the GET param and then authenticates it with the KeyManager

type Result

type Result struct {
	JobRunID  string      `json:"jobRunId"`
	ID        string      `json:"id,omitempty"`
	TaskRunID string      `json:"taskRunId,omitempty"`
	Status    string      `json:"status"`
	Error     null.String `json:"error"`
	Pending   bool        `json:"pending"`
	Data      *JSON       `json:"data"`
}

Result represents a Chainlink JobRun

func (*Result) SetCompleted

func (r *Result) SetCompleted()

SetCompleted marks a result as completed

func (*Result) SetErrored

func (r *Result) SetErrored(err error)

SetCompleted marks a result as errored

func (*Result) SetJobRunID

func (r *Result) SetJobRunID()

SetJobRunID sets the request's ID to the result's Job Run ID. If "jobRunId" is supplied in the request, use that for the response.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server holds pointers to the bridges indexed by their paths and the bridge to be mounted in Lambda.

func NewServer

func NewServer(bridges ...Bridge) *Server

NewServer returns a new Server with the bridges in a map indexed by its path. Once returned, the server can be started to listen for any new requests.

If a bridge is passed in that has a duplicate path then the last one with that path will be mounted.

Any bridge with an empty path gets assigned "/" to avoid panics on start.

func (*Server) Handler

func (s *Server) Handler(w http.ResponseWriter, r *http.Request)

Hander is of http.Handler type, receiving any inbound requests from the HTTP server when the bridge is ran local

func (*Server) Lambda

func (s *Server) Lambda(r *Result) (interface{}, error)

func (*Server) Mux

func (s *Server) Mux() http.Handler

HTTPMux returns the http.Handler for the go http multiplexer, registering all the bridges paths with the handler

func (*Server) Start

func (s *Server) Start(port int)

Start the bridge server. Routing on how the server is started is determined which platform is specified by the end user. Currently supporting:

  • Inbuilt http (default)
  • AWS Lambda (env LAMBDA=1)

Port only has to be passed in if the inbuilt HTTP server is being used.

If the inbuilt http server is being used, bridges can specify many external adaptors as long if exclusive paths are given.

If multiple adaptors are included with Lambda/gcp enabled, then the first bridge that has it enabled will be given as the Handler.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL