<commit_before>package empire

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/remind101/empire/empire/pkg/container"
	"github.com/remind101/empire/empire/pkg/pod"
	"github.com/remind101/pkg/timex"
	"golang.org/x/net/context"
)

type ContainerRelay struct {
	Name      string `json:"name"`
	AttachURL string `json:"attach_url"`
	Command   string `json:"command"`
	State     string `json:"state"`
	Type      string
	Size      string
	CreatedAt time.Time `json:"created_at"`
}

// containerRelayer defines an interface for running a container
// remotely.
type containerRelayer interface {
	Relay(context.Context, *container.Container) (*ContainerRelay, error)
}

type fakeRelayer struct{}

func (f *fakeRelayer) Relay(ctx context.Context, c *container.Container) (*ContainerRelay, error) {
	return &ContainerRelay{
		Name:      "run.123",
		AttachURL: "fake://example.com:5000/abc",
		Command:   c.Command,
		State:     "starting",
		Type:      "run",
		Size:      "1X",
		CreatedAt: timex.Now(),
	}, nil
}

type relayer struct {
	API string // Location of the relay http api.
}

type postContainersForm struct {
	Image   string            `json:"image"`
	Command string            `json:"command"`
	Env     map[string]string `json:"env"`
	Attach  bool              `json:"attach"`
}

func (r *relayer) Relay(ctx context.Context, c *container.Container) (*ContainerRelay, error) {
	f := &postContainersForm{
		Image:   c.Image.String(),
		Command: c.Command,
		Env:     c.Env,
		Attach:  true,
	}

	b, err := json.Marshal(f)
	if err != nil {
		return nil, err
	}

	url := fmt.Sprintf("%s/containers", r.API)
	resp, err := http.Post(url, "application/json", bytes.NewReader(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	cr := &ContainerRelay{}
	rb, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(rb, cr)
	if err != nil {
		return nil, err
	}

	return cr, nil
}

type runner struct {
	store   *store
	relayer containerRelayer
}

func (r *runner) Run(ctx context.Context, app *App, command string, opts ProcessesRunOpts) (*ContainerRelay, error) {
	c, err := r.newContainer(ctx, app, command, opts)
	if err != nil {
		return nil, err
	}

	return r.relayer.Relay(ctx, c)
}

func (r *runner) newContainer(ctx context.Context, app *App, command string, opts ProcessesRunOpts) (*container.Container, error) {
	release, err := r.store.ReleasesLast(app)
	if err != nil {
		return nil, err
	}

	if release == nil {
		return nil, &ValidationError{Err: fmt.Errorf("no releases for %s", app.Name)}
	}

	config, err := r.store.ConfigsFind(release.ConfigID)
	if err != nil {
		return nil, err
	}

	slug, err := r.store.SlugsFind(release.SlugID)
	if err != nil {
		return nil, err
	}

	process := &Process{
		Type:     "run",
		Command:  Command(command),
		Quantity: 1,
	}

	vars := Vars{}
	for key, val := range opts.Env {
		vars[Variable(key)] = val
	}

	t := newTemplate(release, NewConfig(config, vars), slug, process)
	i := pod.NewInstance(t, 1)
	c := pod.NewContainer(i)
	return c, nil
}
<commit_msg>Remove dependency on pkg.pod and pkg.container<commit_after>package empire

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/remind101/pkg/timex"
	"golang.org/x/net/context"
)

type ContainerRelay struct {
	Name      string `json:"name"`
	AttachURL string `json:"attach_url"`
	Command   string `json:"command"`
	State     string `json:"state"`
	Type      string
	Size      string
	CreatedAt time.Time `json:"created_at"`
}

type postContainersForm struct {
	User    string            `json:"user"`
	Image   string            `json:"image"`
	Command string            `json:"command"`
	Env     map[string]string `json:"env"`
	Attach  bool              `json:"attach"`
}

// containerRelayer defines an interface for running a container
// remotely.
type containerRelayer interface {
	Relay(context.Context, *postContainersForm) (*ContainerRelay, error)
}

type fakeRelayer struct{}

func (f *fakeRelayer) Relay(ctx context.Context, c *postContainersForm) (*ContainerRelay, error) {
	return &ContainerRelay{
		Name:      "run.123",
		AttachURL: "fake://example.com:5000/abc",
		Command:   c.Command,
		State:     "starting",
		Type:      "run",
		Size:      "1X",
		CreatedAt: timex.Now(),
	}, nil
}

type relayer struct {
	API string // Location of the relay http api.
}

func (r *relayer) Relay(ctx context.Context, f *postContainersForm) (*ContainerRelay, error) {
	b, err := json.Marshal(f)
	if err != nil {
		return nil, err
	}

	url := fmt.Sprintf("%s/containers", r.API)
	resp, err := http.Post(url, "application/json", bytes.NewReader(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	cr := &ContainerRelay{}
	rb, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(rb, cr)
	if err != nil {
		return nil, err
	}

	return cr, nil
}

type runner struct {
	store   *store
	relayer containerRelayer
}

func (r *runner) Run(ctx context.Context, app *App, command string, opts ProcessesRunOpts) (*ContainerRelay, error) {
	c, err := r.newContainerForm(ctx, app, command, opts)
	if err != nil {
		return nil, err
	}

	return r.relayer.Relay(ctx, c)
}

func (r *runner) newContainerForm(ctx context.Context, app *App, command string, opts ProcessesRunOpts) (*postContainersForm, error) {
	release, err := r.store.ReleasesLast(app)
	if err != nil {
		return nil, err
	}

	if release == nil {
		return nil, &ValidationError{Err: fmt.Errorf("no releases for %s", app.Name)}
	}

	config, err := r.store.ConfigsFind(release.ConfigID)
	if err != nil {
		return nil, err
	}

	slug, err := r.store.SlugsFind(release.SlugID)
	if err != nil {
		return nil, err
	}

	// Merge env vars with App env vars.
	vars := Vars{}
	for key, val := range opts.Env {
		vars[Variable(key)] = val
	}
	env := environment(NewConfig(config, vars).Vars)

	username := ""
	if user, ok := UserFromContext(ctx); ok {
		username = user.Name
	}

	c := &postContainersForm{
		User:    username,
		Image:   slug.Image.String(),
		Command: command,
		Env:     env,
		Attach:  true,
	}

	return c, nil
}
<|endoftext|>


<commit_before>package charm

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
)

// InfoResponse is sent by the charm store in response to charm-info requests.
type InfoResponse struct {
	Revision int      `json:"revision"` // Zero is valid. Can't omitempty.
	Sha256   string   `json:"sha256,omitempty"`
	Digest   string   `json:"digest,omitempty"`
	Errors   []string `json:"errors,omitempty"`
	Warnings []string `json:"warnings,omitempty"`
}

// EventResponse is sent by the charm store in response to charm-event requests.
type EventResponse struct {
	Kind     string   `json:"kind"`
	Revision int      `json:"revision"` // Zero is valid. Can't omitempty.
	Digest   string   `json:"digest,omitempty"`
	Errors   []string `json:"errors,omitempty"`
	Warnings []string `json:"warnings,omitempty"`
	Time     string   `json:"time,omitempty"`
}

// Repository respresents a collection of charms.
type Repository interface {
	Get(curl *URL) (Charm, error)
	Latest(curl *URL) (int, error)
}

// NotFoundError represents an error indicating that the requested data wasn't found.
type NotFoundError struct {
	msg string
}

func (e *NotFoundError) Error() string {
	return e.msg
}

// CharmStore is a Repository that provides access to the public juju charm store.
type CharmStore struct {
	baseURL string
}

var Store = &CharmStore{"https://store.juju.ubuntu.com"}

// Info returns details for a charm in the charm store.
func (s *CharmStore) Info(curl *URL) (*InfoResponse, error) {
	key := curl.String()
	resp, err := http.Get(s.baseURL + "/charm-info?charms=" + url.QueryEscape(key))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	infos := make(map[string]*InfoResponse)
	if err = json.Unmarshal(body, &infos); err != nil {
		return nil, err
	}
	info, found := infos[key]
	if !found {
		return nil, fmt.Errorf("charm: charm store returned response without charm %q", key)
	}
	if len(info.Errors) == 1 && info.Errors[0] == "entry not found" {
		return nil, &NotFoundError{fmt.Sprintf("charm not found: %s", curl)}
	}
	return info, nil
}

// Event returns details for a charm event in the charm store.
//
// If digest is empty, the latest event is returned.
func (s *CharmStore) Event(curl *URL, digest string) (*EventResponse, error) {
	key := curl.String()
	query := key
	if digest != "" {
		query += "@" + digest
	}
	resp, err := http.Get(s.baseURL + "/charm-event?charms=" + url.QueryEscape(query))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	events := make(map[string]*EventResponse)
	if err = json.Unmarshal(body, &events); err != nil {
		return nil, err
	}
	event, found := events[key]
	if !found {
		return nil, fmt.Errorf("charm: charm store returned response without charm %q", key)
	}
	if len(event.Errors) == 1 && event.Errors[0] == "entry not found" {
		if digest == "" {
			return nil, &NotFoundError{fmt.Sprintf("charm event not found for %q", curl)}
		} else {
			return nil, &NotFoundError{fmt.Sprintf("charm event not found for %q with digest %q", curl, digest)}
		}
	}
	return event, nil
}

// revision returns the revision and SHA256 digest of the charm referenced by curl.
func (s *CharmStore) revision(curl *URL) (revision int, digest string, err error) {
	info, err := s.Info(curl)
	if err != nil {
		return 0, "", err
	}
	for _, w := range info.Warnings {
		log.Warningf("charm: charm store reports for %q: %s", curl, w)
	}
	if info.Errors != nil {
		return 0, "", fmt.Errorf("charm info errors for %q: %s", curl, strings.Join(info.Errors, "; "))
	}
	return info.Revision, info.Sha256, nil
}

// Latest returns the latest revision of the charm referenced by curl, regardless
// of the revision set on curl itself.
func (s *CharmStore) Latest(curl *URL) (int, error) {
	rev, _, err := s.revision(curl.WithRevision(-1))
	return rev, err
}

// BranchLocation returns the location for the branch holding the charm at curl.
func (s *CharmStore) BranchLocation(curl *URL) string {
	if curl.User != "" {
		return fmt.Sprintf("lp:~%s/charms/%s/%s/trunk", curl.User, curl.Series, curl.Name)
	}
	return fmt.Sprintf("lp:charms/%s/%s/trunk", curl.Series, curl.Name)
}

var branchPrefixes = []string{
	"lp:",
	"bzr+ssh://bazaar.launchpad.net/+branch/",
	"bzr+ssh://bazaar.launchpad.net/",
	"http://launchpad.net/+branch/",
	"http://launchpad.net/",
	"https://launchpad.net/+branch/",
	"https://launchpad.net/",
	"http://code.launchpad.net/+branch/",
	"http://code.launchpad.net/",
	"https://code.launchpad.net/+branch/",
	"https://code.launchpad.net/",
}

// CharmURL returns the charm URL for the branch at location.
func (s *CharmStore) CharmURL(location string) (*URL, error) {
	var l string
	if len(location) > 0 && location[0] == '~' {
		l = location
	} else {
		for _, prefix := range branchPrefixes {
			if strings.HasPrefix(location, prefix) {
				l = location[len(prefix):]
				break
			}
		}
	}
	if l != "" {
		u := strings.Split(l, "/")
		if len(u) == 3 && u[0] == "charms" {
			return ParseURL(fmt.Sprintf("cs:%s/%s", u[1], u[2]))
		}
		if len(u) == 4 && u[0] == "charms" && u[3] == "trunk" {
			return ParseURL(fmt.Sprintf("cs:%s/%s", u[1], u[2]))
		}
		if len(u) == 5 && u[1] == "charms" && u[4] == "trunk" && len(u[0]) > 0 && u[0][0] == '~' {
			return ParseURL(fmt.Sprintf("cs:%s/%s/%s", u[0], u[2], u[3]))
		}
	}
	return nil, fmt.Errorf("unknown branch location: %q", location)
}

// verify returns an error unless a file exists at path with a hex-encoded
// SHA256 matching digest.
func verify(path, digest string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return err
	}
	if hex.EncodeToString(h.Sum(nil)) != digest {
		return fmt.Errorf("bad SHA256 of %q", path)
	}
	return nil
}

// Get returns the charm referenced by curl.
func (s *CharmStore) Get(curl *URL) (Charm, error) {
	cachePath := config.JujuHomePath("cache")
	if err := os.MkdirAll(cachePath, 0755); err != nil {
		return nil, err
	}
	rev, digest, err := s.revision(curl)
	if err != nil {
		return nil, err
	}
	if curl.Revision == -1 {
		curl = curl.WithRevision(rev)
	} else if curl.Revision != rev {
		return nil, fmt.Errorf("charm: store returned charm with wrong revision for %q", curl.String())
	}
	path := filepath.Join(cachePath, Quote(curl.String())+".charm")
	if verify(path, digest) != nil {
		resp, err := http.Get(s.baseURL + "/charm/" + url.QueryEscape(curl.Path()))
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
		f, err := ioutil.TempFile(cachePath, "charm-download")
		if err != nil {
			return nil, err
		}
		dlPath := f.Name()
		_, err = io.Copy(f, resp.Body)
		if cerr := f.Close(); err == nil {
			err = cerr
		}
		if err != nil {
			os.Remove(dlPath)
			return nil, err
		}
		if err := os.Rename(dlPath, path); err != nil {
			return nil, err
		}
	}
	if err := verify(path, digest); err != nil {
		return nil, err
	}
	return ReadBundle(path)
}

// LocalRepository represents a local directory containing subdirectories
// named after an Ubuntu series, each of which contains charms targeted for
// that series. For example:
//
//   /path/to/repository/oneiric/mongodb/
//   /path/to/repository/precise/mongodb.charm
//   /path/to/repository/precise/wordpress/
type LocalRepository struct {
	Path string
}

// Latest returns the latest revision of the charm referenced by curl, regardless
// of the revision set on curl itself.
func (r *LocalRepository) Latest(curl *URL) (int, error) {
	ch, err := r.Get(curl.WithRevision(-1))
	if err != nil {
		return 0, err
	}
	return ch.Revision(), nil
}

func repoNotFound(path string) error {
	return &NotFoundError{fmt.Sprintf("no repository found at %q", path)}
}

func charmNotFound(curl *URL, repoPath string) error {
	return &NotFoundError{fmt.Sprintf("no charms found matching %q in %s", curl, repoPath)}
}

func mightBeCharm(info os.FileInfo) bool {
	if info.IsDir() {
		return !strings.HasPrefix(info.Name(), ".")
	}
	return strings.HasSuffix(info.Name(), ".charm")
}

// Get returns a charm matching curl, if one exists. If curl has a revision of
// -1, it returns the latest charm that matches curl. If multiple candidates
// satisfy the foregoing, the first one encountered will be returned.
func (r *LocalRepository) Get(curl *URL) (Charm, error) {
	if curl.Schema != "local" {
		return nil, fmt.Errorf("local repository got URL with non-local schema: %q", curl)
	}
	info, err := os.Stat(r.Path)
	if err != nil {
		if os.IsNotExist(err) {
			err = repoNotFound(r.Path)
		}
		return nil, err
	}
	if !info.IsDir() {
		return nil, repoNotFound(r.Path)
	}
	path := filepath.Join(r.Path, curl.Series)
	infos, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, charmNotFound(curl, r.Path)
	}
	var latest Charm
	for _, info := range infos {
		if !mightBeCharm(info) {
			continue
		}
		chPath := filepath.Join(path, info.Name())
		if ch, err := Read(chPath); err != nil {
			log.Warningf("charm: failed to load charm at %q: %s", chPath, err)
		} else if ch.Meta().Name == curl.Name {
			if ch.Revision() == curl.Revision {
				return ch, nil
			}
			if latest == nil || ch.Revision() > latest.Revision() {
				latest = ch
			}
		}
	}
	if curl.Revision == -1 && latest != nil {
		return latest, nil
	}
	return nil, charmNotFound(curl, r.Path)
}
<commit_msg>Prototype global charm cache directory.<commit_after>package charm

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/juju-core/log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
)

// CacheDir stores the charm cache directory path.
var CacheDir string

// InfoResponse is sent by the charm store in response to charm-info requests.
type InfoResponse struct {
	Revision int      `json:"revision"` // Zero is valid. Can't omitempty.
	Sha256   string   `json:"sha256,omitempty"`
	Digest   string   `json:"digest,omitempty"`
	Errors   []string `json:"errors,omitempty"`
	Warnings []string `json:"warnings,omitempty"`
}

// EventResponse is sent by the charm store in response to charm-event requests.
type EventResponse struct {
	Kind     string   `json:"kind"`
	Revision int      `json:"revision"` // Zero is valid. Can't omitempty.
	Digest   string   `json:"digest,omitempty"`
	Errors   []string `json:"errors,omitempty"`
	Warnings []string `json:"warnings,omitempty"`
	Time     string   `json:"time,omitempty"`
}

// Repository respresents a collection of charms.
type Repository interface {
	Get(curl *URL) (Charm, error)
	Latest(curl *URL) (int, error)
}

// NotFoundError represents an error indicating that the requested data wasn't found.
type NotFoundError struct {
	msg string
}

func (e *NotFoundError) Error() string {
	return e.msg
}

// CharmStore is a Repository that provides access to the public juju charm store.
type CharmStore struct {
	baseURL string
}

var Store = &CharmStore{"https://store.juju.ubuntu.com"}

// Info returns details for a charm in the charm store.
func (s *CharmStore) Info(curl *URL) (*InfoResponse, error) {
	key := curl.String()
	resp, err := http.Get(s.baseURL + "/charm-info?charms=" + url.QueryEscape(key))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	infos := make(map[string]*InfoResponse)
	if err = json.Unmarshal(body, &infos); err != nil {
		return nil, err
	}
	info, found := infos[key]
	if !found {
		return nil, fmt.Errorf("charm: charm store returned response without charm %q", key)
	}
	if len(info.Errors) == 1 && info.Errors[0] == "entry not found" {
		return nil, &NotFoundError{fmt.Sprintf("charm not found: %s", curl)}
	}
	return info, nil
}

// Event returns details for a charm event in the charm store.
//
// If digest is empty, the latest event is returned.
func (s *CharmStore) Event(curl *URL, digest string) (*EventResponse, error) {
	key := curl.String()
	query := key
	if digest != "" {
		query += "@" + digest
	}
	resp, err := http.Get(s.baseURL + "/charm-event?charms=" + url.QueryEscape(query))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	events := make(map[string]*EventResponse)
	if err = json.Unmarshal(body, &events); err != nil {
		return nil, err
	}
	event, found := events[key]
	if !found {
		return nil, fmt.Errorf("charm: charm store returned response without charm %q", key)
	}
	if len(event.Errors) == 1 && event.Errors[0] == "entry not found" {
		if digest == "" {
			return nil, &NotFoundError{fmt.Sprintf("charm event not found for %q", curl)}
		} else {
			return nil, &NotFoundError{fmt.Sprintf("charm event not found for %q with digest %q", curl, digest)}
		}
	}
	return event, nil
}

// revision returns the revision and SHA256 digest of the charm referenced by curl.
func (s *CharmStore) revision(curl *URL) (revision int, digest string, err error) {
	info, err := s.Info(curl)
	if err != nil {
		return 0, "", err
	}
	for _, w := range info.Warnings {
		log.Warningf("charm: charm store reports for %q: %s", curl, w)
	}
	if info.Errors != nil {
		return 0, "", fmt.Errorf("charm info errors for %q: %s", curl, strings.Join(info.Errors, "; "))
	}
	return info.Revision, info.Sha256, nil
}

// Latest returns the latest revision of the charm referenced by curl, regardless
// of the revision set on curl itself.
func (s *CharmStore) Latest(curl *URL) (int, error) {
	rev, _, err := s.revision(curl.WithRevision(-1))
	return rev, err
}

// BranchLocation returns the location for the branch holding the charm at curl.
func (s *CharmStore) BranchLocation(curl *URL) string {
	if curl.User != "" {
		return fmt.Sprintf("lp:~%s/charms/%s/%s/trunk", curl.User, curl.Series, curl.Name)
	}
	return fmt.Sprintf("lp:charms/%s/%s/trunk", curl.Series, curl.Name)
}

var branchPrefixes = []string{
	"lp:",
	"bzr+ssh://bazaar.launchpad.net/+branch/",
	"bzr+ssh://bazaar.launchpad.net/",
	"http://launchpad.net/+branch/",
	"http://launchpad.net/",
	"https://launchpad.net/+branch/",
	"https://launchpad.net/",
	"http://code.launchpad.net/+branch/",
	"http://code.launchpad.net/",
	"https://code.launchpad.net/+branch/",
	"https://code.launchpad.net/",
}

// CharmURL returns the charm URL for the branch at location.
func (s *CharmStore) CharmURL(location string) (*URL, error) {
	var l string
	if len(location) > 0 && location[0] == '~' {
		l = location
	} else {
		for _, prefix := range branchPrefixes {
			if strings.HasPrefix(location, prefix) {
				l = location[len(prefix):]
				break
			}
		}
	}
	if l != "" {
		u := strings.Split(l, "/")
		if len(u) == 3 && u[0] == "charms" {
			return ParseURL(fmt.Sprintf("cs:%s/%s", u[1], u[2]))
		}
		if len(u) == 4 && u[0] == "charms" && u[3] == "trunk" {
			return ParseURL(fmt.Sprintf("cs:%s/%s", u[1], u[2]))
		}
		if len(u) == 5 && u[1] == "charms" && u[4] == "trunk" && len(u[0]) > 0 && u[0][0] == '~' {
			return ParseURL(fmt.Sprintf("cs:%s/%s/%s", u[0], u[2], u[3]))
		}
	}
	return nil, fmt.Errorf("unknown branch location: %q", location)
}

// verify returns an error unless a file exists at path with a hex-encoded
// SHA256 matching digest.
func verify(path, digest string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return err
	}
	if hex.EncodeToString(h.Sum(nil)) != digest {
		return fmt.Errorf("bad SHA256 of %q", path)
	}
	return nil
}

// Get returns the charm referenced by curl.
func (s *CharmStore) Get(curl *URL) (Charm, error) {
	// MachineAgent.Run should have already set the CacheDir.
	if CacheDir == "" {
		panic("the charm cache directory path is empty")
	}
	if err := os.MkdirAll(CacheDir, 0755); err != nil {
		return nil, err
	}
	rev, digest, err := s.revision(curl)
	if err != nil {
		return nil, err
	}
	if curl.Revision == -1 {
		curl = curl.WithRevision(rev)
	} else if curl.Revision != rev {
		return nil, fmt.Errorf("charm: store returned charm with wrong revision for %q", curl.String())
	}
	path := filepath.Join(CacheDir, Quote(curl.String())+".charm")
	if verify(path, digest) != nil {
		resp, err := http.Get(s.baseURL + "/charm/" + url.QueryEscape(curl.Path()))
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
		f, err := ioutil.TempFile(CacheDir, "charm-download")
		if err != nil {
			return nil, err
		}
		dlPath := f.Name()
		_, err = io.Copy(f, resp.Body)
		if cerr := f.Close(); err == nil {
			err = cerr
		}
		if err != nil {
			os.Remove(dlPath)
			return nil, err
		}
		if err := os.Rename(dlPath, path); err != nil {
			return nil, err
		}
	}
	if err := verify(path, digest); err != nil {
		return nil, err
	}
	return ReadBundle(path)
}

// LocalRepository represents a local directory containing subdirectories
// named after an Ubuntu series, each of which contains charms targeted for
// that series. For example:
//
//   /path/to/repository/oneiric/mongodb/
//   /path/to/repository/precise/mongodb.charm
//   /path/to/repository/precise/wordpress/
type LocalRepository struct {
	Path string
}

// Latest returns the latest revision of the charm referenced by curl, regardless
// of the revision set on curl itself.
func (r *LocalRepository) Latest(curl *URL) (int, error) {
	ch, err := r.Get(curl.WithRevision(-1))
	if err != nil {
		return 0, err
	}
	return ch.Revision(), nil
}

func repoNotFound(path string) error {
	return &NotFoundError{fmt.Sprintf("no repository found at %q", path)}
}

func charmNotFound(curl *URL, repoPath string) error {
	return &NotFoundError{fmt.Sprintf("no charms found matching %q in %s", curl, repoPath)}
}

func mightBeCharm(info os.FileInfo) bool {
	if info.IsDir() {
		return !strings.HasPrefix(info.Name(), ".")
	}
	return strings.HasSuffix(info.Name(), ".charm")
}

// Get returns a charm matching curl, if one exists. If curl has a revision of
// -1, it returns the latest charm that matches curl. If multiple candidates
// satisfy the foregoing, the first one encountered will be returned.
func (r *LocalRepository) Get(curl *URL) (Charm, error) {
	if curl.Schema != "local" {
		return nil, fmt.Errorf("local repository got URL with non-local schema: %q", curl)
	}
	info, err := os.Stat(r.Path)
	if err != nil {
		if os.IsNotExist(err) {
			err = repoNotFound(r.Path)
		}
		return nil, err
	}
	if !info.IsDir() {
		return nil, repoNotFound(r.Path)
	}
	path := filepath.Join(r.Path, curl.Series)
	infos, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, charmNotFound(curl, r.Path)
	}
	var latest Charm
	for _, info := range infos {
		if !mightBeCharm(info) {
			continue
		}
		chPath := filepath.Join(path, info.Name())
		if ch, err := Read(chPath); err != nil {
			log.Warningf("charm: failed to load charm at %q: %s", chPath, err)
		} else if ch.Meta().Name == curl.Name {
			if ch.Revision() == curl.Revision {
				return ch, nil
			}
			if latest == nil || ch.Revision() > latest.Revision() {
				latest = ch
			}
		}
	}
	if curl.Revision == -1 && latest != nil {
		return latest, nil
	}
	return nil, charmNotFound(curl, r.Path)
}
<|endoftext|>


<commit_before>package engine

import (
	"fmt"
	"strconv"

	"github.com/disintegration/imaging"
	"github.com/imdario/mergo"

	"github.com/thoas/picfit/engine/backend"
	"github.com/thoas/picfit/image"
)

var defaultParams = map[string]string{
	"upscale": "1",
	"h":       "0",
	"w":       "0",
	"deg":     "90",
}

var formats = map[string]imaging.Format{
	"jpeg": imaging.JPEG,
	"jpg":  imaging.JPEG,
	"png":  imaging.PNG,
	"gif":  imaging.GIF,
	"bmp":  imaging.BMP,
}

type Engine struct {
	DefaultFormat  string
	Format         string
	DefaultQuality int

	b backend.Backend
}

const (
	goEngineType       = "go"
	lilliputEngineType = "lilliput"
)

// Options is the engine options
type Options struct {
	Upscale  bool
	Format   imaging.Format
	Quality  int
	Width    int
	Height   int
	Position string
	Degree   int
}

// New initializes an Engine
func New(cfg Config) *Engine {
	var back backend.Backend
	if cfg.Type == lilliputEngineType {
		back = backend.NewLilliputEngine(cfg.MaxBufferSize)
	} else {
		back = &backend.GoImageEngine{}
	}
	return &Engine{
		DefaultFormat:  cfg.DefaultFormat,
		Format:         cfg.Format,
		DefaultQuality: cfg.Quality,
		b:              back,
	}
}

func (e *Engine) Transform(img *image.ImageFile, operation Operation, qs map[string]string) (*image.ImageFile, error) {
	err := mergo.Merge(&qs, defaultParams)

	if err != nil {
		return nil, err
	}

	var quality int
	var format string

	q, ok := qs["q"]

	if ok {
		quality, err := strconv.Atoi(q)

		if err != nil {
			return nil, err
		}

		if quality > 100 {
			return nil, fmt.Errorf("Quality should be <= 100")
		}
	} else {
		quality = e.DefaultQuality
	}

	format, ok = qs["fmt"]
	filepath := img.Filepath

	if ok {
		if _, ok := ContentTypes[format]; !ok {
			return nil, fmt.Errorf("Unknown format %s", format)
		}

	}

	if format == "" && e.Format != "" {
		format = e.Format
	}

	if format == "" {
		format = img.Format()
	}

	if format == "" {
		format = e.DefaultFormat
	}

	if format != img.Format() {
		index := len(filepath) - len(img.Format())

		filepath = filepath[:index] + format

		if contentType, ok := ContentTypes[format]; ok {
			img.Headers["Content-Type"] = contentType
		}
	}

	file := &image.ImageFile{
		Source:   img.Source,
		Key:      img.Key,
		Headers:  img.Headers,
		Filepath: filepath,
	}

	options := &backend.Options{
		Quality: quality,
		Format:  formats[format],
	}

	var content []byte
	switch operation {
	case Noop:
		file.Processed = file.Source

		return file, err
	case Flip:
		pos, ok := qs["pos"]
		if !ok {
			return nil, fmt.Errorf("Parameter \"pos\" not found in query string")
		}

		options.Position = pos
		content, err = e.b.Flip(img, options)

	case Rotate:
		deg, err := strconv.Atoi(qs["deg"])
		if err != nil {
			return nil, err
		}

		options.Degree = deg
		content, err = e.b.Rotate(img, options)

	case Thumbnail, Resize, Fit:
		var upscale bool
		var w int
		var h int

		if upscale, err = strconv.ParseBool(qs["upscale"]); err != nil {
			return nil, err
		}

		if w, err = strconv.Atoi(qs["w"]); err != nil {
			return nil, err
		}

		if h, err = strconv.Atoi(qs["h"]); err != nil {
			return nil, err
		}

		options.Width = w
		options.Height = h
		options.Upscale = upscale

		switch operation {
		case Resize:
			content, err = e.b.Resize(img, options)
		case Thumbnail:
			content, err = e.b.Thumbnail(img, options)
		case Fit:
			content, err = e.b.Fit(img, options)
		}

	default:
		return nil, fmt.Errorf("Operation not found for %s", operation)
	}

	if err != nil {
		return nil, err
	}

	file.Processed = content

	return file, err
}
<commit_msg>fix engine: remove duplicate Options<commit_after>package engine

import (
	"fmt"
	"strconv"

	"github.com/disintegration/imaging"
	"github.com/imdario/mergo"

	"github.com/thoas/picfit/engine/backend"
	"github.com/thoas/picfit/image"
)

var defaultParams = map[string]string{
	"upscale": "1",
	"h":       "0",
	"w":       "0",
	"deg":     "90",
}

var formats = map[string]imaging.Format{
	"jpeg": imaging.JPEG,
	"jpg":  imaging.JPEG,
	"png":  imaging.PNG,
	"gif":  imaging.GIF,
	"bmp":  imaging.BMP,
}

type Engine struct {
	DefaultFormat  string
	Format         string
	DefaultQuality int

	b backend.Backend
}

const (
	goEngineType       = "go"
	lilliputEngineType = "lilliput"
)

// New initializes an Engine
func New(cfg Config) *Engine {
	var back backend.Backend
	if cfg.Type == lilliputEngineType {
		back = backend.NewLilliputEngine(cfg.MaxBufferSize)
	} else {
		back = &backend.GoImageEngine{}
	}
	return &Engine{
		DefaultFormat:  cfg.DefaultFormat,
		Format:         cfg.Format,
		DefaultQuality: cfg.Quality,
		b:              back,
	}
}

func (e *Engine) Transform(img *image.ImageFile, operation Operation, qs map[string]string) (*image.ImageFile, error) {
	err := mergo.Merge(&qs, defaultParams)

	if err != nil {
		return nil, err
	}

	var quality int
	var format string

	q, ok := qs["q"]

	if ok {
		quality, err := strconv.Atoi(q)

		if err != nil {
			return nil, err
		}

		if quality > 100 {
			return nil, fmt.Errorf("Quality should be <= 100")
		}
	} else {
		quality = e.DefaultQuality
	}

	format, ok = qs["fmt"]
	filepath := img.Filepath

	if ok {
		if _, ok := ContentTypes[format]; !ok {
			return nil, fmt.Errorf("Unknown format %s", format)
		}

	}

	if format == "" && e.Format != "" {
		format = e.Format
	}

	if format == "" {
		format = img.Format()
	}

	if format == "" {
		format = e.DefaultFormat
	}

	if format != img.Format() {
		index := len(filepath) - len(img.Format())

		filepath = filepath[:index] + format

		if contentType, ok := ContentTypes[format]; ok {
			img.Headers["Content-Type"] = contentType
		}
	}

	file := &image.ImageFile{
		Source:   img.Source,
		Key:      img.Key,
		Headers:  img.Headers,
		Filepath: filepath,
	}

	options := &backend.Options{
		Quality: quality,
		Format:  formats[format],
	}

	var content []byte
	switch operation {
	case Noop:
		file.Processed = file.Source

		return file, err
	case Flip:
		pos, ok := qs["pos"]
		if !ok {
			return nil, fmt.Errorf("Parameter \"pos\" not found in query string")
		}

		options.Position = pos
		content, err = e.b.Flip(img, options)

	case Rotate:
		deg, err := strconv.Atoi(qs["deg"])
		if err != nil {
			return nil, err
		}

		options.Degree = deg
		content, err = e.b.Rotate(img, options)

	case Thumbnail, Resize, Fit:
		var upscale bool
		var w int
		var h int

		if upscale, err = strconv.ParseBool(qs["upscale"]); err != nil {
			return nil, err
		}

		if w, err = strconv.Atoi(qs["w"]); err != nil {
			return nil, err
		}

		if h, err = strconv.Atoi(qs["h"]); err != nil {
			return nil, err
		}

		options.Width = w
		options.Height = h
		options.Upscale = upscale

		switch operation {
		case Resize:
			content, err = e.b.Resize(img, options)
		case Thumbnail:
			content, err = e.b.Thumbnail(img, options)
		case Fit:
			content, err = e.b.Fit(img, options)
		}

	default:
		return nil, fmt.Errorf("Operation not found for %s", operation)
	}

	if err != nil {
		return nil, err
	}

	file.Processed = content

	return file, err
}
<|endoftext|>


<commit_before>// Copyright 2013 Artur Grabowski. All rights reserved.
// Use of this source code is governed by a ISC-style
// license that can be found in the LICENSE file.
package engine

import (
	"bsearch/index"
	"bsearch/parser"
	"fmt"
	"log"
	"net"
	"github.com/art4711/bconf"
	"github.com/art4711/timers"
	"bufio"
)

type headers map[string]string

func (h headers) Add(k, v string) {
	h[k] = v
}

type EngineState struct {
	Conf bconf.Bconf
	Index *index.Index
	Timer *timers.Timer

}

func (s EngineState) Listener() {
	listenport := s.Conf.GetString("port", "search")

	ln, err := net.Listen("tcp", ":" + listenport)
	if err != nil {
		log.Fatal("listen: %v\n", err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal("accept %v\n", err)
		}
		go s.handle(conn)
	}
}

func (s EngineState) handle(conn net.Conn) {
	defer conn.Close()
	qt := s.Timer.Start("query")
	defer qt.Stop()

	b := [2048]byte{}
	n, err := conn.Read(b[:])
	if err != nil {
		log.Fatal("conn.Read %v\n", err)
	}
	bq := b[:n]

	writer := bufio.NewWriter(conn)
	defer writer.Flush()

	pt := qt.Start("parse")
	q, err := parser.Parse(s.Index, string(bq))
	if err != nil {
		fmt.Fprintf(writer, "info:error:%v\n", err)
		return
	}

	docarr := make([]*index.IbDoc, 0)

	pt = pt.Handover("performQuery")
	search := index.NullDoc()
	for {
		d := q.NextDoc(search)
		if d == nil {
			break
		}
		docarr = append(docarr, d)
		*search = *d
		search.Inc()
	}
	pt = pt.Handover("ProcessHeaders")
	h := make(headers)
	q.ProcessHeaders(h)

	pt = pt.Handover("writeHeaders")
	for k, v := range h {
		fmt.Fprintf(writer, "info:%v:%v\n", k, v)
	}
	writer.WriteString(s.Index.Header())
	pt = pt.Handover("writeDocs")
	for o, d := range docarr {
		if d == nil {
			log.Fatalf("nil in docarr at %v", o)
		}
		doc, exists := s.Index.Docs[d.Id]
		if !exists {
			log.Printf("Doc %v does not exist", d.Id)
		}
		writer.Write(doc)
		writer.WriteString("\n")
	}
	pt.Stop()
}
<commit_msg>Better names for timers.<commit_after>// Copyright 2013 Artur Grabowski. All rights reserved.
// Use of this source code is governed by a ISC-style
// license that can be found in the LICENSE file.
package engine

import (
	"bsearch/index"
	"bsearch/parser"
	"fmt"
	"log"
	"net"
	"github.com/art4711/bconf"
	"github.com/art4711/timers"
	"bufio"
)

type headers map[string]string

func (h headers) Add(k, v string) {
	h[k] = v
}

type EngineState struct {
	Conf bconf.Bconf
	Index *index.Index
	Timer *timers.Timer

}

func (s EngineState) Listener() {
	listenport := s.Conf.GetString("port", "search")

	ln, err := net.Listen("tcp", ":" + listenport)
	if err != nil {
		log.Fatal("listen: %v\n", err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal("accept %v\n", err)
		}
		go s.handle(conn)
	}
}

func (s EngineState) handle(conn net.Conn) {
	defer conn.Close()
	qt := s.Timer.Start("query")
	defer qt.Stop()

	et := qt.Start("read")
	b := [2048]byte{}
	n, err := conn.Read(b[:])
	if err != nil {
		log.Fatal("conn.Read %v\n", err)
	}
	bq := b[:n]

	writer := bufio.NewWriter(conn)
	defer writer.Flush()

	et = et.Handover("parse")
	q, err := parser.Parse(s.Index, string(bq))
	if err != nil {
		fmt.Fprintf(writer, "info:error:%v\n", err)
		et.Stop()
		return
	}

	docarr := make([]*index.IbDoc, 0)

	et = et.Handover("performQuery")
	search := index.NullDoc()
	for {
		d := q.NextDoc(search)
		if d == nil {
			break
		}
		docarr = append(docarr, d)
		*search = *d
		search.Inc()
	}
	et = et.Handover("ProcessHeaders")
	h := make(headers)
	q.ProcessHeaders(h)

	et = et.Handover("writeHeaders")
	for k, v := range h {
		fmt.Fprintf(writer, "info:%v:%v\n", k, v)
	}
	writer.WriteString(s.Index.Header())
	et = et.Handover("writeDocs")
	for o, d := range docarr {
		if d == nil {
			log.Fatalf("nil in docarr at %v", o)
		}
		doc, exists := s.Index.Docs[d.Id]
		if !exists {
			log.Printf("Doc %v does not exist", d.Id)
		}
		writer.Write(doc)
		writer.WriteString("\n")
	}
	et.Stop()
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2017 Daniel J. Stroot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package utility is a small library of go utility functions.  It can round
// floats, durations, check equivalency of floats, generate random hexadecimal
// strings, find the index value of a given slice value, make file names, etc.
package utility

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"math"
	"math/rand"
	"net"
	"os"
	"path/filepath"
	"time"
)

const (
	timeFormat = "2006-01-02T15-04-05.000"
	dateFormat = "060102"
)

// Check streamlines error checks - use it *only*
// when the program should halt
func Check(e error) {
	if e != nil {
		log.Printf("FATAL: %+v\n", e)
		os.Exit(1)
	}
}

// SliceIndex figures out the index of slice given a value in the slice.
// http://stackoverflow.com/questions/8307478/go-how-to-find-out-element-position-in-slice
// It's used to find the slice position of the process activity step.
// Then we use that to calculate the next action.  Usage:
//
// xs := []int{2, 4, 6, 8}
// ys := []string{"C", "B", "K", "A"}
// fmt.Println(
//     SliceIndex(len(xs), func(i int) bool { return xs[i] == 5 }),
//     SliceIndex(len(xs), func(i int) bool { return xs[i] == 6 }),
//     SliceIndex(len(ys), func(i int) bool { return ys[i] == "Z" }),
//     SliceIndex(len(ys), func(i int) bool { return ys[i] == "A" }))
func SliceIndex(limit int, predicate func(i int) bool) (int, error) {
	for i := 0; i < limit; i++ {
		if predicate(i) {
			return i, nil
		}
	}
	return -1, errors.New("not found in slice")
}

// RoundFloat64 rounds numbers
func RoundFloat64(val float64, places int) float64 {
	var round float64
	pow := math.Pow(10, float64(places))
	digit := pow * val
	_, div := math.Modf(digit)
	if div >= float64(.5) {
		round = math.Ceil(digit)
	} else {
		round = math.Floor(digit)
	}
	return round / pow
}

// RoundDuration rounds time.Duration
// Example to seconds: RoundDuration(d, time.Second)
func RoundDuration(d, r time.Duration) time.Duration {
	if r <= 0 {
		return d
	}
	neg := d < 0
	if neg {
		d = -d
	}
	if m := d % r; m+m < r {
		d = d - m
	} else {
		d = d + r - m
	}
	if neg {
		return -d
	}
	return d
}

// Float64Equal checks not whether the numbers are exactly the same, but
// whether their difference is very small. The error margin that the
// difference is compared to is called epsilon.
func Float64Equal(a, b float64) bool {
	const epsilon float64 = 0.00000001
	if (a-b) < epsilon && (b-a) < epsilon {
		return true
	}
	return false
}

// MakeFileName creates a new filename
func MakeFileName(fileExtension string, directory string) string {
	t := time.Now().UTC()
	timestamp := t.Format(timeFormat)
	return filepath.Join(directory, fmt.Sprintf("%s%s", timestamp, fileExtension))
}

// GetLocalIP returns the non-loopback local IP of the host
func GetLocalIP() (ip string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "", err
	}
	for _, address := range addrs {
		// check the address type (not a loopback)
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				ip = ipnet.IP.String()
				return ip, nil
			}
		}
	}
	return "", errors.New("no ip address found")
}

// Padding takes a string, length and either "left" or "right" for
// padding/justification plus a padding character.  It will pad the string out.
func Padding(s string, length int, justified string, padChar string) (string, error) {
	paddingNeeded := length - len(s)
	paddedString := ""

	// Check for errors
	if len(s) > length {
		return "", errors.New("string is too long")
	}
	if len(padChar) > 1 {
		return "", errors.New("padding must be only one character")
	}

	if justified == "right" {
		for i := 0; i < paddingNeeded; i++ {
			paddedString = paddedString + padChar
		}
		paddedString = paddedString + s
		return paddedString, nil
	}

	if justified == "left" {
		paddedString = s
		for i := 0; i < paddingNeeded; i++ {
			paddedString = paddedString + padChar
		}
		return paddedString, nil
	}

	return "", errors.New("justification must be either right or left")
}

// CalcSettlementDate takes in today's date (basically "now") along with a
// a map of bank holidays and it calculates to correct settlement date for
// ACH transactions.
func CalcSettlementDate(today time.Time, bankHolidayMap map[string]bool) time.Time {

	// settlement is tomorrow.
	settlementDate := today.AddDate(0, 0, 1)

	// unless tomorrow is Saturday, then it's Monday
	if "Saturday" == settlementDate.Weekday().String() {
		// add two more days (cover the weekend)
		settlementDate = settlementDate.AddDate(0, 0, 2)
	}

	// unless tomorrow is Sunday, then it's Monday
	if "Sunday" == settlementDate.Weekday().String() {
		// add one more day (cover the weekend)
		settlementDate = settlementDate.AddDate(0, 0, 1)
	}

	// unless the calculated settlement day is a
	// holiday, then add one more day.
	date := settlementDate.Format(dateFormat)
	_, found := bankHolidayMap[date]
	if found {
		settlementDate = settlementDate.AddDate(0, 0, 1)

		if "Saturday" == settlementDate.Weekday().String() {
			// add two more days (cover the weekend)
			settlementDate = settlementDate.AddDate(0, 0, 2)
		}

		if "Sunday" == settlementDate.Weekday().String() {
			// add one more day (cover the weekend)
			settlementDate = settlementDate.AddDate(0, 0, 1)
		}
	}
	return settlementDate
}

// GenRandomString returns a hexadecimal random string
func GenRandomString(strlen int) string {
	if strlen <= 0 {
		return ""
	}

	const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
	rand.Seed(time.Now().UTC().UnixNano())
	result := make([]byte, strlen)

	// make randon string
	for i := 0; i < strlen; i++ {
		result[i] = chars[rand.Intn(len(chars))]
	}

	// hex encode string
	hexString := hex.EncodeToString(result)
	return hexString
}
<commit_msg>License<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2017 Daniel J. Stroot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package utility is a small library of go utility functions.  It can round
// floats, durations, check equivalency of floats, generate random hexadecimal
// strings, find the index value of a given slice value, make file names, etc.
package utility

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"math"
	"math/rand"
	"net"
	"os"
	"path/filepath"
	"time"
)

const (
	timeFormat = "2006-01-02T15-04-05.000"
	dateFormat = "060102"
)

// Check streamlines error checks - use it *only*
// when the program should halt
func Check(e error) {
	if e != nil {
		log.Printf("FATAL: %+v\n", e)
		os.Exit(1)
	}
}

// SliceIndex figures out the index of slice given a value in the slice.
// http://stackoverflow.com/questions/8307478/go-how-to-find-out-element-position-in-slice
// It's used to find the slice position of the process activity step.
// Then we use that to calculate the next action.  Usage:
//     xs := []int{2, 4, 6, 8}
//     ys := []string{"C", "B", "K", "A"}
//     fmt.Println(
//         SliceIndex(len(xs), func(i int) bool { return xs[i] == 5 }),
//         SliceIndex(len(xs), func(i int) bool { return xs[i] == 6 }),
//         SliceIndex(len(ys), func(i int) bool { return ys[i] == "Z" }),
//         SliceIndex(len(ys), func(i int) bool { return ys[i] == "A" })
//     )
func SliceIndex(limit int, predicate func(i int) bool) (int, error) {
	for i := 0; i < limit; i++ {
		if predicate(i) {
			return i, nil
		}
	}
	return -1, errors.New("not found in slice")
}

// RoundFloat64 rounds numbers
func RoundFloat64(val float64, places int) float64 {
	var round float64
	pow := math.Pow(10, float64(places))
	digit := pow * val
	_, div := math.Modf(digit)
	if div >= float64(.5) {
		round = math.Ceil(digit)
	} else {
		round = math.Floor(digit)
	}
	return round / pow
}

// RoundDuration rounds time.Duration
// Example to seconds: RoundDuration(d, time.Second)
func RoundDuration(d, r time.Duration) time.Duration {
	if r <= 0 {
		return d
	}
	neg := d < 0
	if neg {
		d = -d
	}
	if m := d % r; m+m < r {
		d = d - m
	} else {
		d = d + r - m
	}
	if neg {
		return -d
	}
	return d
}

// Float64Equal checks not whether the numbers are exactly the same, but
// whether their difference is very small. The error margin that the
// difference is compared to is called epsilon.
func Float64Equal(a, b float64) bool {
	const epsilon float64 = 0.00000001
	if (a-b) < epsilon && (b-a) < epsilon {
		return true
	}
	return false
}

// MakeFileName creates a new filename
func MakeFileName(fileExtension string, directory string) string {
	t := time.Now().UTC()
	timestamp := t.Format(timeFormat)
	return filepath.Join(directory, fmt.Sprintf("%s%s", timestamp, fileExtension))
}

// GetLocalIP returns the non-loopback local IP of the host
func GetLocalIP() (ip string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "", err
	}
	for _, address := range addrs {
		// check the address type (not a loopback)
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				ip = ipnet.IP.String()
				return ip, nil
			}
		}
	}
	return "", errors.New("no ip address found")
}

// Padding takes a string, length and either "left" or "right" for
// padding/justification plus a padding character.  It will pad the string out.
func Padding(s string, length int, justified string, padChar string) (string, error) {
	paddingNeeded := length - len(s)
	paddedString := ""

	// Check for errors
	if len(s) > length {
		return "", errors.New("string is too long")
	}
	if len(padChar) > 1 {
		return "", errors.New("padding must be only one character")
	}

	if justified == "right" {
		for i := 0; i < paddingNeeded; i++ {
			paddedString = paddedString + padChar
		}
		paddedString = paddedString + s
		return paddedString, nil
	}

	if justified == "left" {
		paddedString = s
		for i := 0; i < paddingNeeded; i++ {
			paddedString = paddedString + padChar
		}
		return paddedString, nil
	}

	return "", errors.New("justification must be either right or left")
}

// CalcSettlementDate takes in today's date (basically "now") along with a
// a map of bank holidays and it calculates to correct settlement date for
// ACH transactions.
func CalcSettlementDate(today time.Time, bankHolidayMap map[string]bool) time.Time {

	// settlement is tomorrow.
	settlementDate := today.AddDate(0, 0, 1)

	// unless tomorrow is Saturday, then it's Monday
	if "Saturday" == settlementDate.Weekday().String() {
		// add two more days (cover the weekend)
		settlementDate = settlementDate.AddDate(0, 0, 2)
	}

	// unless tomorrow is Sunday, then it's Monday
	if "Sunday" == settlementDate.Weekday().String() {
		// add one more day (cover the weekend)
		settlementDate = settlementDate.AddDate(0, 0, 1)
	}

	// unless the calculated settlement day is a
	// holiday, then add one more day.
	date := settlementDate.Format(dateFormat)
	_, found := bankHolidayMap[date]
	if found {
		settlementDate = settlementDate.AddDate(0, 0, 1)

		if "Saturday" == settlementDate.Weekday().String() {
			// add two more days (cover the weekend)
			settlementDate = settlementDate.AddDate(0, 0, 2)
		}

		if "Sunday" == settlementDate.Weekday().String() {
			// add one more day (cover the weekend)
			settlementDate = settlementDate.AddDate(0, 0, 1)
		}
	}
	return settlementDate
}

// GenRandomString returns a hexadecimal random string
func GenRandomString(strlen int) string {
	if strlen <= 0 {
		return ""
	}

	const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
	rand.Seed(time.Now().UTC().UnixNano())
	result := make([]byte, strlen)

	// make randon string
	for i := 0; i < strlen; i++ {
		result[i] = chars[rand.Intn(len(chars))]
	}

	// hex encode string
	hexString := hex.EncodeToString(result)
	return hexString
}
<|endoftext|>


<commit_before>package itest

import (
	"context"
	"encoding/hex"
	"fmt"
	"sync"
	"time"

	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/lightningnetwork/lnd"
	"github.com/lightningnetwork/lnd/chainreg"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/lightningnetwork/lnd/routing/route"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

var (
	customTestKey   uint64 = 394829
	customTestValue        = []byte{1, 3, 5}
)

type interceptorTestCase struct {
	amountMsat        int64
	payAddr           []byte
	invoice           *lnrpc.Invoice
	shouldHold        bool
	interceptorAction routerrpc.ResolveHoldForwardAction
}

// testForwardInterceptor tests the forward interceptor RPC layer.
// The test creates a cluster of 3 connected nodes: Alice -> Bob -> Carol
// Alice sends 4 different payments to Carol while the interceptor handles
// differently the htlcs.
// The test ensures that:
// 1. Intercepted failed htlcs result in no payment (invoice is not settled).
// 2. Intercepted resumed htlcs result in a payment (invoice is settled).
// 3. Intercepted held htlcs result in no payment (invoice is not settled).
// 4. When Interceptor disconnects it resumes all held htlcs, which result in
//    valid payment (invoice is settled).
func testForwardInterceptor(net *lntest.NetworkHarness, t *harnessTest) {
	// initialize the test context with 3 connected nodes.
	testContext := newInterceptorTestContext(t, net)
	defer testContext.shutdownNodes()

	const (
		chanAmt = btcutil.Amount(300000)
	)

	// Open and wait for channels.
	testContext.openChannel(testContext.alice, testContext.bob, chanAmt)
	testContext.openChannel(testContext.bob, testContext.carol, chanAmt)
	defer testContext.closeChannels()
	testContext.waitForChannels()

	// Connect the interceptor.
	ctx := context.Background()
	ctxt, cancelInterceptor := context.WithTimeout(ctx, defaultTimeout)
	interceptor, err := testContext.bob.RouterClient.HtlcInterceptor(ctxt)
	require.NoError(t.t, err, "failed to create HtlcInterceptor")

	// Prepare the test cases.
	testCases := testContext.prepareTestCases()

	// A channel for the interceptor go routine to send the requested packets.
	interceptedChan := make(chan *routerrpc.ForwardHtlcInterceptRequest,
		len(testCases))

	// Run the interceptor loop in its own go routine.
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			request, err := interceptor.Recv()
			if err != nil {
				// If it is  just the error result of the context cancellation
				// the we exit silently.
				status, ok := status.FromError(err)
				if ok && status.Code() == codes.Canceled {
					return
				}
				// Otherwise it an unexpected error, we fail the test.
				require.NoError(t.t, err, "unexpected error in interceptor.Recv()")
				return
			}
			interceptedChan <- request
		}
	}()

	// For each test case make sure we initiate a payment from Alice to Carol
	// routed through Bob. For each payment we also test its final status
	// according to the interceptorAction specified in the test case.
	wg.Add(1)
	go func() {
		defer wg.Done()
		for _, tc := range testCases {
			attempt, err := testContext.sendAliceToCarolPayment(
				context.Background(), tc.invoice.ValueMsat,
				tc.invoice.RHash, tc.payAddr,
			)

			if t.t.Failed() {
				return
			}
			if err != nil {
				require.NoError(t.t, err, "failed to send payment")
			}

			switch tc.interceptorAction {
			// For 'fail' interceptor action we make sure the payment failed.
			case routerrpc.ResolveHoldForwardAction_FAIL:
				require.Equal(t.t, lnrpc.HTLCAttempt_FAILED,
					attempt.Status, "expected payment to fail")

			// For settle and resume we make sure the payment is successful.
			case routerrpc.ResolveHoldForwardAction_SETTLE:
				fallthrough

			case routerrpc.ResolveHoldForwardAction_RESUME:
				require.Equal(t.t, lnrpc.HTLCAttempt_SUCCEEDED,
					attempt.Status, "expected payment to succeed")
			}
		}
	}()

	// We make sure here the interceptor has processed all packets before we
	// check the payment statuses.
	for i := 0; i < len(testCases); i++ {
		select {
		case request := <-interceptedChan:
			// Assert sanity of informational packet data.
			require.NotZero(t.t, request.OutgoingRequestedChanId)
			require.NotZero(t.t, request.IncomingExpiry)
			require.NotZero(t.t, request.IncomingAmountMsat)

			require.Less(
				t.t,
				request.OutgoingExpiry, request.IncomingExpiry,
			)
			require.Less(
				t.t,
				request.OutgoingAmountMsat,
				request.IncomingAmountMsat,
			)

			value, ok := request.CustomRecords[customTestKey]
			require.True(t.t, ok, "expected custom record")
			require.Equal(t.t, customTestValue, value)

			testCase := testCases[i]

			// For held packets we ignore, keeping them in hold status.
			if testCase.shouldHold {
				continue
			}

			// For all other packets we resolve according to the test case.
			_ = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
				IncomingCircuitKey: request.IncomingCircuitKey,
				Action:             testCase.interceptorAction,
				Preimage:           testCase.invoice.RPreimage,
			})
		case <-time.After(defaultTimeout):
			t.Fatalf("response from interceptor was not received %v", i)
		}
	}

	// At this point we are left with the held packets, we want to make sure
	// each one of them has a corresponding 'in-flight' payment at
	// Alice's node.
	payments, err := testContext.alice.ListPayments(context.Background(),
		&lnrpc.ListPaymentsRequest{IncludeIncomplete: true})
	require.NoError(t.t, err, "failed to fetch payment")

	for _, testCase := range testCases {
		if testCase.shouldHold {
			hashStr := hex.EncodeToString(testCase.invoice.RHash)
			var foundPayment *lnrpc.Payment
			expectedAmt := testCase.invoice.ValueMsat
			for _, p := range payments.Payments {
				if p.PaymentHash == hashStr {
					foundPayment = p
					break
				}
			}
			require.NotNil(t.t, foundPayment, fmt.Sprintf("expected "+
				"to find pending payment for held htlc %v",
				hashStr))
			require.Equal(t.t, lnrpc.Payment_IN_FLIGHT,
				foundPayment.Status, "expected payment to be "+
					"in flight")
			require.Equal(t.t, expectedAmt, foundPayment.ValueMsat,
				"incorrect in flight amount")
		}
	}

	// Disconnect interceptor should cause resume held packets.
	// After that we wait for all go routines to finish, including the one
	// that tests the payment final status for the held payment.
	cancelInterceptor()
	wg.Wait()
}

// interceptorTestContext is a helper struct to hold the test context and
// provide the needed functionality.
type interceptorTestContext struct {
	t   *harnessTest
	net *lntest.NetworkHarness

	// Keep a list of all our active channels.
	networkChans      []*lnrpc.ChannelPoint
	closeChannelFuncs []func()

	alice, bob, carol *lntest.HarnessNode
	nodes             []*lntest.HarnessNode
}

func newInterceptorTestContext(t *harnessTest,
	net *lntest.NetworkHarness) *interceptorTestContext {

	ctxb := context.Background()

	// Create a three-node context consisting of Alice, Bob and Carol
	carol, err := net.NewNode("carol", nil)
	require.NoError(t.t, err, "unable to create carol")

	// Connect nodes
	nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol}
	for i := 0; i < len(nodes); i++ {
		for j := i + 1; j < len(nodes); j++ {
			ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
			err = net.EnsureConnected(ctxt, nodes[i], nodes[j])
			require.NoError(t.t, err, "unable to connect nodes")
		}
	}

	ctx := interceptorTestContext{
		t:     t,
		net:   net,
		alice: net.Alice,
		bob:   net.Bob,
		carol: carol,
		nodes: nodes,
	}

	return &ctx
}

// prepareTestCases prepares 4 tests:
// 1. failed htlc.
// 2. resumed htlc.
// 3. settling htlc externally.
// 4. held htlc that is resumed later.
func (c *interceptorTestContext) prepareTestCases() []*interceptorTestCase {
	cases := []*interceptorTestCase{
		{amountMsat: 1000, shouldHold: false,
			interceptorAction: routerrpc.ResolveHoldForwardAction_FAIL},
		{amountMsat: 1000, shouldHold: false,
			interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME},
		{amountMsat: 1000, shouldHold: false,
			interceptorAction: routerrpc.ResolveHoldForwardAction_SETTLE},
		{amountMsat: 1000, shouldHold: true,
			interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME},
	}

	for _, t := range cases {
		addResponse, err := c.carol.AddInvoice(context.Background(), &lnrpc.Invoice{
			ValueMsat: t.amountMsat,
		})
		require.NoError(c.t.t, err, "unable to add invoice")

		invoice, err := c.carol.LookupInvoice(context.Background(), &lnrpc.PaymentHash{
			RHashStr: hex.EncodeToString(addResponse.RHash),
		})
		require.NoError(c.t.t, err, "unable to find invoice")

		// We'll need to also decode the returned invoice so we can
		// grab the payment address which is now required for ALL
		// payments.
		payReq, err := c.carol.DecodePayReq(context.Background(), &lnrpc.PayReqString{
			PayReq: invoice.PaymentRequest,
		})
		require.NoError(c.t.t, err, "unable to decode invoice")

		t.invoice = invoice
		t.payAddr = payReq.PaymentAddr
	}
	return cases
}

func (c *interceptorTestContext) openChannel(from, to *lntest.HarnessNode,
	chanSize btcutil.Amount) {

	ctxb := context.Background()

	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
	err := c.net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, from)
	require.NoError(c.t.t, err, "unable to send coins")

	ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
	chanPoint := openChannelAndAssert(
		ctxt, c.t, c.net, from, to,
		lntest.OpenChannelParams{
			Amt: chanSize,
		},
	)

	c.closeChannelFuncs = append(c.closeChannelFuncs, func() {
		ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
		closeChannelAndAssert(
			ctxt, c.t, c.net, from, chanPoint, false,
		)
	})

	c.networkChans = append(c.networkChans, chanPoint)
}

func (c *interceptorTestContext) closeChannels() {
	for _, f := range c.closeChannelFuncs {
		f()
	}
}

func (c *interceptorTestContext) shutdownNodes() {
	shutdownAndAssert(c.net, c.t, c.carol)
}

func (c *interceptorTestContext) waitForChannels() {
	ctxb := context.Background()

	// Wait for all nodes to have seen all channels.
	for _, chanPoint := range c.networkChans {
		for _, node := range c.nodes {
			txid, err := lnd.GetChanPointFundingTxid(chanPoint)
			require.NoError(c.t.t, err, "unable to get txid")

			point := wire.OutPoint{
				Hash:  *txid,
				Index: chanPoint.OutputIndex,
			}

			ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
			err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
			require.NoError(c.t.t, err, fmt.Sprintf("(%d): timeout "+
				"waiting for channel(%s) open", node.NodeID,
				point))
		}
	}
}

// sendAliceToCarolPayment sends a payment from alice to carol and make an
// attempt to pay. The lnrpc.HTLCAttempt is returned.
func (c *interceptorTestContext) sendAliceToCarolPayment(ctx context.Context,
	amtMsat int64,
	paymentHash, paymentAddr []byte) (*lnrpc.HTLCAttempt, error) {

	// Build a route from alice to carol.
	route, err := c.buildRoute(
		ctx, amtMsat, []*lntest.HarnessNode{c.bob, c.carol},
		paymentAddr,
	)
	if err != nil {
		return nil, err
	}
	sendReq := &routerrpc.SendToRouteRequest{
		PaymentHash: paymentHash,
		Route:       route,
	}

	// Send a custom record to the forwarding node.
	route.Hops[0].CustomRecords = map[uint64][]byte{
		customTestKey: customTestValue,
	}

	// Send the payment.
	return c.alice.RouterClient.SendToRouteV2(ctx, sendReq)
}

// buildRoute is a helper function to build a route with given hops.
func (c *interceptorTestContext) buildRoute(ctx context.Context, amtMsat int64,
	hops []*lntest.HarnessNode, payAddr []byte) (*lnrpc.Route, error) {

	rpcHops := make([][]byte, 0, len(hops))
	for _, hop := range hops {
		k := hop.PubKeyStr
		pubkey, err := route.NewVertexFromStr(k)
		if err != nil {
			return nil, fmt.Errorf("error parsing %v: %v",
				k, err)
		}
		rpcHops = append(rpcHops, pubkey[:])
	}

	req := &routerrpc.BuildRouteRequest{
		AmtMsat:        amtMsat,
		FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
		HopPubkeys:     rpcHops,
		PaymentAddr:    payAddr,
	}

	routeResp, err := c.alice.RouterClient.BuildRoute(ctx, req)
	if err != nil {
		return nil, err
	}

	return routeResp.Route, nil
}
<commit_msg>itest: use fresh alice and bob for fwd interceptor tests<commit_after>package itest

import (
	"context"
	"encoding/hex"
	"fmt"
	"sync"
	"time"

	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/lightningnetwork/lnd"
	"github.com/lightningnetwork/lnd/chainreg"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/lightningnetwork/lnd/routing/route"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

var (
	customTestKey   uint64 = 394829
	customTestValue        = []byte{1, 3, 5}
)

type interceptorTestCase struct {
	amountMsat        int64
	payAddr           []byte
	invoice           *lnrpc.Invoice
	shouldHold        bool
	interceptorAction routerrpc.ResolveHoldForwardAction
}

// testForwardInterceptor tests the forward interceptor RPC layer.
// The test creates a cluster of 3 connected nodes: Alice -> Bob -> Carol
// Alice sends 4 different payments to Carol while the interceptor handles
// differently the htlcs.
// The test ensures that:
// 1. Intercepted failed htlcs result in no payment (invoice is not settled).
// 2. Intercepted resumed htlcs result in a payment (invoice is settled).
// 3. Intercepted held htlcs result in no payment (invoice is not settled).
// 4. When Interceptor disconnects it resumes all held htlcs, which result in
//    valid payment (invoice is settled).
func testForwardInterceptor(net *lntest.NetworkHarness, t *harnessTest) {
	// initialize the test context with 3 connected nodes.
	testContext := newInterceptorTestContext(t, net)
	defer testContext.shutdownNodes()

	const (
		chanAmt = btcutil.Amount(300000)
	)

	// Open and wait for channels.
	testContext.openChannel(testContext.alice, testContext.bob, chanAmt)
	testContext.openChannel(testContext.bob, testContext.carol, chanAmt)
	defer testContext.closeChannels()
	testContext.waitForChannels()

	// Connect the interceptor.
	ctx := context.Background()
	ctxt, cancelInterceptor := context.WithTimeout(ctx, defaultTimeout)
	interceptor, err := testContext.bob.RouterClient.HtlcInterceptor(ctxt)
	require.NoError(t.t, err, "failed to create HtlcInterceptor")

	// Prepare the test cases.
	testCases := testContext.prepareTestCases()

	// A channel for the interceptor go routine to send the requested packets.
	interceptedChan := make(chan *routerrpc.ForwardHtlcInterceptRequest,
		len(testCases))

	// Run the interceptor loop in its own go routine.
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			request, err := interceptor.Recv()
			if err != nil {
				// If it is  just the error result of the context cancellation
				// the we exit silently.
				status, ok := status.FromError(err)
				if ok && status.Code() == codes.Canceled {
					return
				}
				// Otherwise it an unexpected error, we fail the test.
				require.NoError(t.t, err, "unexpected error in interceptor.Recv()")
				return
			}
			interceptedChan <- request
		}
	}()

	// For each test case make sure we initiate a payment from Alice to Carol
	// routed through Bob. For each payment we also test its final status
	// according to the interceptorAction specified in the test case.
	wg.Add(1)
	go func() {
		defer wg.Done()
		for _, tc := range testCases {
			attempt, err := testContext.sendAliceToCarolPayment(
				context.Background(), tc.invoice.ValueMsat,
				tc.invoice.RHash, tc.payAddr,
			)

			if t.t.Failed() {
				return
			}
			if err != nil {
				require.NoError(t.t, err, "failed to send payment")
			}

			switch tc.interceptorAction {
			// For 'fail' interceptor action we make sure the payment failed.
			case routerrpc.ResolveHoldForwardAction_FAIL:
				require.Equal(t.t, lnrpc.HTLCAttempt_FAILED,
					attempt.Status, "expected payment to fail")

			// For settle and resume we make sure the payment is successful.
			case routerrpc.ResolveHoldForwardAction_SETTLE:
				fallthrough

			case routerrpc.ResolveHoldForwardAction_RESUME:
				require.Equal(t.t, lnrpc.HTLCAttempt_SUCCEEDED,
					attempt.Status, "expected payment to succeed")
			}
		}
	}()

	// We make sure here the interceptor has processed all packets before we
	// check the payment statuses.
	for i := 0; i < len(testCases); i++ {
		select {
		case request := <-interceptedChan:
			// Assert sanity of informational packet data.
			require.NotZero(t.t, request.OutgoingRequestedChanId)
			require.NotZero(t.t, request.IncomingExpiry)
			require.NotZero(t.t, request.IncomingAmountMsat)

			require.Less(
				t.t,
				request.OutgoingExpiry, request.IncomingExpiry,
			)
			require.Less(
				t.t,
				request.OutgoingAmountMsat,
				request.IncomingAmountMsat,
			)

			value, ok := request.CustomRecords[customTestKey]
			require.True(t.t, ok, "expected custom record")
			require.Equal(t.t, customTestValue, value)

			testCase := testCases[i]

			// For held packets we ignore, keeping them in hold status.
			if testCase.shouldHold {
				continue
			}

			// For all other packets we resolve according to the test case.
			_ = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
				IncomingCircuitKey: request.IncomingCircuitKey,
				Action:             testCase.interceptorAction,
				Preimage:           testCase.invoice.RPreimage,
			})
		case <-time.After(defaultTimeout):
			t.Fatalf("response from interceptor was not received %v", i)
		}
	}

	// At this point we are left with the held packets, we want to make sure
	// each one of them has a corresponding 'in-flight' payment at
	// Alice's node.
	payments, err := testContext.alice.ListPayments(context.Background(),
		&lnrpc.ListPaymentsRequest{IncludeIncomplete: true})
	require.NoError(t.t, err, "failed to fetch payment")

	for _, testCase := range testCases {
		if testCase.shouldHold {
			hashStr := hex.EncodeToString(testCase.invoice.RHash)
			var foundPayment *lnrpc.Payment
			expectedAmt := testCase.invoice.ValueMsat
			for _, p := range payments.Payments {
				if p.PaymentHash == hashStr {
					foundPayment = p
					break
				}
			}
			require.NotNil(t.t, foundPayment, fmt.Sprintf("expected "+
				"to find pending payment for held htlc %v",
				hashStr))
			require.Equal(t.t, lnrpc.Payment_IN_FLIGHT,
				foundPayment.Status, "expected payment to be "+
					"in flight")
			require.Equal(t.t, expectedAmt, foundPayment.ValueMsat,
				"incorrect in flight amount")
		}
	}

	// Disconnect interceptor should cause resume held packets.
	// After that we wait for all go routines to finish, including the one
	// that tests the payment final status for the held payment.
	cancelInterceptor()
	wg.Wait()
}

// interceptorTestContext is a helper struct to hold the test context and
// provide the needed functionality.
type interceptorTestContext struct {
	t   *harnessTest
	net *lntest.NetworkHarness

	// Keep a list of all our active channels.
	networkChans      []*lnrpc.ChannelPoint
	closeChannelFuncs []func()

	alice, bob, carol *lntest.HarnessNode
	nodes             []*lntest.HarnessNode
}

func newInterceptorTestContext(t *harnessTest,
	net *lntest.NetworkHarness) *interceptorTestContext {

	ctxb := context.Background()

	// Create a three-node context consisting of Alice, Bob and Carol
	alice, err := net.NewNode("alice", nil)
	require.NoError(t.t, err, "unable to create alice")
	bob, err := net.NewNode("bob", nil)
	require.NoError(t.t, err, "unable to create bob")
	carol, err := net.NewNode("carol", nil)
	require.NoError(t.t, err, "unable to create carol")

	// Connect nodes
	nodes := []*lntest.HarnessNode{alice, bob, carol}
	for i := 0; i < len(nodes); i++ {
		for j := i + 1; j < len(nodes); j++ {
			ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
			err = net.EnsureConnected(ctxt, nodes[i], nodes[j])
			require.NoError(t.t, err, "unable to connect nodes")
		}
	}

	ctx := interceptorTestContext{
		t:     t,
		net:   net,
		alice: alice,
		bob:   bob,
		carol: carol,
		nodes: nodes,
	}

	return &ctx
}

// prepareTestCases prepares 4 tests:
// 1. failed htlc.
// 2. resumed htlc.
// 3. settling htlc externally.
// 4. held htlc that is resumed later.
func (c *interceptorTestContext) prepareTestCases() []*interceptorTestCase {
	cases := []*interceptorTestCase{
		{amountMsat: 1000, shouldHold: false,
			interceptorAction: routerrpc.ResolveHoldForwardAction_FAIL},
		{amountMsat: 1000, shouldHold: false,
			interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME},
		{amountMsat: 1000, shouldHold: false,
			interceptorAction: routerrpc.ResolveHoldForwardAction_SETTLE},
		{amountMsat: 1000, shouldHold: true,
			interceptorAction: routerrpc.ResolveHoldForwardAction_RESUME},
	}

	for _, t := range cases {
		addResponse, err := c.carol.AddInvoice(context.Background(), &lnrpc.Invoice{
			ValueMsat: t.amountMsat,
		})
		require.NoError(c.t.t, err, "unable to add invoice")

		invoice, err := c.carol.LookupInvoice(context.Background(), &lnrpc.PaymentHash{
			RHashStr: hex.EncodeToString(addResponse.RHash),
		})
		require.NoError(c.t.t, err, "unable to find invoice")

		// We'll need to also decode the returned invoice so we can
		// grab the payment address which is now required for ALL
		// payments.
		payReq, err := c.carol.DecodePayReq(context.Background(), &lnrpc.PayReqString{
			PayReq: invoice.PaymentRequest,
		})
		require.NoError(c.t.t, err, "unable to decode invoice")

		t.invoice = invoice
		t.payAddr = payReq.PaymentAddr
	}
	return cases
}

func (c *interceptorTestContext) openChannel(from, to *lntest.HarnessNode,
	chanSize btcutil.Amount) {

	ctxb := context.Background()

	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
	err := c.net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, from)
	require.NoError(c.t.t, err, "unable to send coins")

	ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
	chanPoint := openChannelAndAssert(
		ctxt, c.t, c.net, from, to,
		lntest.OpenChannelParams{
			Amt: chanSize,
		},
	)

	c.closeChannelFuncs = append(c.closeChannelFuncs, func() {
		ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
		closeChannelAndAssert(
			ctxt, c.t, c.net, from, chanPoint, false,
		)
	})

	c.networkChans = append(c.networkChans, chanPoint)
}

func (c *interceptorTestContext) closeChannels() {
	for _, f := range c.closeChannelFuncs {
		f()
	}
}

func (c *interceptorTestContext) shutdownNodes() {
	shutdownAndAssert(c.net, c.t, c.alice)
	shutdownAndAssert(c.net, c.t, c.bob)
	shutdownAndAssert(c.net, c.t, c.carol)
}

func (c *interceptorTestContext) waitForChannels() {
	ctxb := context.Background()

	// Wait for all nodes to have seen all channels.
	for _, chanPoint := range c.networkChans {
		for _, node := range c.nodes {
			txid, err := lnd.GetChanPointFundingTxid(chanPoint)
			require.NoError(c.t.t, err, "unable to get txid")

			point := wire.OutPoint{
				Hash:  *txid,
				Index: chanPoint.OutputIndex,
			}

			ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
			err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
			require.NoError(c.t.t, err, fmt.Sprintf("(%d): timeout "+
				"waiting for channel(%s) open", node.NodeID,
				point))
		}
	}
}

// sendAliceToCarolPayment sends a payment from alice to carol and make an
// attempt to pay. The lnrpc.HTLCAttempt is returned.
func (c *interceptorTestContext) sendAliceToCarolPayment(ctx context.Context,
	amtMsat int64,
	paymentHash, paymentAddr []byte) (*lnrpc.HTLCAttempt, error) {

	// Build a route from alice to carol.
	route, err := c.buildRoute(
		ctx, amtMsat, []*lntest.HarnessNode{c.bob, c.carol},
		paymentAddr,
	)
	if err != nil {
		return nil, err
	}
	sendReq := &routerrpc.SendToRouteRequest{
		PaymentHash: paymentHash,
		Route:       route,
	}

	// Send a custom record to the forwarding node.
	route.Hops[0].CustomRecords = map[uint64][]byte{
		customTestKey: customTestValue,
	}

	// Send the payment.
	return c.alice.RouterClient.SendToRouteV2(ctx, sendReq)
}

// buildRoute is a helper function to build a route with given hops.
func (c *interceptorTestContext) buildRoute(ctx context.Context, amtMsat int64,
	hops []*lntest.HarnessNode, payAddr []byte) (*lnrpc.Route, error) {

	rpcHops := make([][]byte, 0, len(hops))
	for _, hop := range hops {
		k := hop.PubKeyStr
		pubkey, err := route.NewVertexFromStr(k)
		if err != nil {
			return nil, fmt.Errorf("error parsing %v: %v",
				k, err)
		}
		rpcHops = append(rpcHops, pubkey[:])
	}

	req := &routerrpc.BuildRouteRequest{
		AmtMsat:        amtMsat,
		FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
		HopPubkeys:     rpcHops,
		PaymentAddr:    payAddr,
	}

	routeResp, err := c.alice.RouterClient.BuildRoute(ctx, req)
	if err != nil {
		return nil, err
	}

	return routeResp.Route, nil
}
<|endoftext|>


<commit_before>package consensus

import (
	"errors"
	"sort"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errSiacoinMiscount = errors.New("consensus set has the wrong number of siacoins given the height")
	errSiafundMiscount = errors.New("consensus set has the wrong number of siafunds")
)

// checkCurrentPath looks at the blocks in the current path and verifies that
// they are all ordered correctly and in the block map.
func (cs *State) checkCurrentPath() error {
	currentNode := cs.currentBlockNode()
	for i := cs.height(); i != 0; i-- {
		// The block should be in the block map.
		_, exists := cs.blockMap[currentNode.block.ID()]
		if !exists {
			return errors.New("current path block not found in block map")
		}
		// Current node should match the id in the current path.
		if currentNode.block.ID() != cs.currentPath[i] {
			return errors.New("current path points to an incorrect block")
		}
		// Height of node needs to be listed correctly.
		if currentNode.height != i {
			return errors.New("node height mismatches its location in the blockchain")
		}
		// Current node's parent needs the right id.
		if currentNode.block.ParentID != currentNode.parent.block.ID() {
			return errors.New("node parent id mismatches actual parent id")
		}

		currentNode = currentNode.parent
	}
	return nil
}

// checkDelayedSiacoinOutputMaps checks that the delayed siacoin output maps
// have the right number of maps at the right heights.
func (cs *State) checkDelayedSiacoinOutputMaps() error {
	expected := 0
	for i := cs.height() + 1; i <= cs.height()+types.MaturityDelay; i++ {
		if !(i > types.MaturityDelay) {
			continue
		}
		_, exists := cs.delayedSiacoinOutputs[i]
		if !exists {
			return errors.New("delayed siacoin outputs are in an inconsistent state")
		}
		expected++
	}
	if len(cs.delayedSiacoinOutputs) != expected {
		return errors.New("delayed siacoin outputs has too many maps")
	}

	return nil
}

// checkSiacoins counts the number of siacoins in the database and verifies
// that it matches the sum of all the coinbases.
func (cs *State) checkSiacoins() error {
	expectedSiacoins := types.ZeroCurrency
	for i := types.BlockHeight(0); i <= cs.height(); i++ {
		expectedSiacoins = expectedSiacoins.Add(types.CalculateCoinbase(i))
	}
	totalSiacoins := types.ZeroCurrency
	for _, sco := range cs.siacoinOutputs {
		totalSiacoins = totalSiacoins.Add(sco.Value)
	}
	for _, fc := range cs.fileContracts {
		totalSiacoins = totalSiacoins.Add(fc.Payout.Sub(fc.Tax()))
	}
	for _, dsoMap := range cs.delayedSiacoinOutputs {
		for _, dso := range dsoMap {
			totalSiacoins = totalSiacoins.Add(dso.Value)
		}
	}
	for _, sfo := range cs.siafundOutputs {
		sfoSiacoins := cs.siafundPool.Sub(sfo.ClaimStart).Div(types.SiafundCount).Mul(sfo.Value)
		totalSiacoins = totalSiacoins.Add(sfoSiacoins)
	}
	if expectedSiacoins.Cmp(totalSiacoins) != 0 {
		return errSiacoinMiscount
	}
	return nil
}

// checkSiafunds counts the siafund outputs and checks that there are 10,000.
func (cs *State) checkSiafunds() error {
	totalSiafunds := types.ZeroCurrency
	for _, sfo := range cs.siafundOutputs {
		totalSiafunds = totalSiafunds.Add(sfo.Value)
	}
	if totalSiafunds.Cmp(types.SiafundCount) != 0 {
		return errSiafundMiscount
	}
	return nil
}

// consensusSetHash returns the Merkle root of the current state of consensus.
func (cs *State) consensusSetHash() crypto.Hash {
	// Items of interest:
	// 1.	genesis block
	// 3.	current height
	// 4.	current target
	// 5.	current depth
	// 6.	current path + diffs
	// 7.	earliest allowed timestamp of next block
	// 8.	unspent siacoin outputs, sorted by id.
	// 9.	open file contracts, sorted by id.
	// 10.	unspent siafund outputs, sorted by id.
	// 11.	delayed siacoin outputs, sorted by height, then sorted by id.
	// 12.	siafund pool

	// Create a slice of hashes representing all items of interest.
	tree := crypto.NewTree()
	tree.PushObject(cs.blockRoot.block)
	tree.PushObject(cs.height())
	tree.PushObject(cs.currentBlockNode().childTarget)
	tree.PushObject(cs.currentBlockNode().depth)
	tree.PushObject(cs.currentBlockNode().earliestChildTimestamp())

	// Add all the blocks in the current path TODO: along with their diffs.
	for i := 0; i < len(cs.currentPath); i++ {
		tree.PushObject(cs.currentPath[types.BlockHeight(i)])
	}

	// Add all of the siacoin outputs, sorted by id.
	var openSiacoinOutputs crypto.HashSlice
	for siacoinOutputID, _ := range cs.siacoinOutputs {
		openSiacoinOutputs = append(openSiacoinOutputs, crypto.Hash(siacoinOutputID))
	}
	sort.Sort(openSiacoinOutputs)
	for _, id := range openSiacoinOutputs {
		sco, _ := cs.siacoinOutputs[types.SiacoinOutputID(id)]
		tree.PushObject(id)
		tree.PushObject(sco)
	}

	// Add all of the file contracts, sorted by id.
	var openFileContracts crypto.HashSlice
	for fileContractID, _ := range cs.fileContracts {
		openFileContracts = append(openFileContracts, crypto.Hash(fileContractID))
	}
	sort.Sort(openFileContracts)
	for _, id := range openFileContracts {
		// Sanity Check - file contract should exist.
		fc, _ := cs.fileContracts[types.FileContractID(id)]
		tree.PushObject(id)
		tree.PushObject(fc)
	}

	// Add all of the siafund outputs, sorted by id.
	var openSiafundOutputs crypto.HashSlice
	for siafundOutputID, _ := range cs.siafundOutputs {
		openSiafundOutputs = append(openSiafundOutputs, crypto.Hash(siafundOutputID))
	}
	sort.Sort(openSiafundOutputs)
	for _, id := range openSiafundOutputs {
		sco, _ := cs.siafundOutputs[types.SiafundOutputID(id)]
		tree.PushObject(id)
		tree.PushObject(sco)
	}

	// Get the set of delayed siacoin outputs, sorted by maturity height then
	// sorted by id and add them.
	for i := cs.height() + 1; i <= cs.height()+types.MaturityDelay; i++ {
		var delayedSiacoinOutputs crypto.HashSlice
		for id := range cs.delayedSiacoinOutputs[i] {
			delayedSiacoinOutputs = append(delayedSiacoinOutputs, crypto.Hash(id))
		}
		sort.Sort(delayedSiacoinOutputs)
		for _, delayedSiacoinOutputID := range delayedSiacoinOutputs {
			delayedSiacoinOutput, _ := cs.delayedSiacoinOutputs[i][types.SiacoinOutputID(delayedSiacoinOutputID)]
			tree.PushObject(delayedSiacoinOutput)
			tree.PushObject(delayedSiacoinOutputID)
		}
	}

	// Add the siafund pool
	tree.PushObject(cs.siafundPool)

	return tree.Root()
}

// checkRewindApply rewinds and reapplies the current block, checking that the
// consensus set hashes meet the expected values.
func (cs *State) checkRewindApply() error {
	// Do nothing if the DEBUG flag is not set.
	if !build.DEBUG {
		return nil
	}

	// Rewind a block, check that the consensus set hash after rewiding is the
	// same as it was before the current block was added, then reapply the
	// block and check that the new consensus set has is the same as originally
	// calculated.
	currentNode := cs.currentBlockNode()
	cs.revertToNode(currentNode.parent)
	if cs.consensusSetHash() != currentNode.parent.consensusSetHash {
		return errors.New("rewinding a block resulted in unexpected consensus set hash")
	}
	cs.applyUntilNode(currentNode)
	if cs.consensusSetHash() != currentNode.consensusSetHash {
		return errors.New("reapplying a block resulted in unexpected consensus set hash")
	}
	return nil
}

// checkConsistency runs a series of checks to make sure that the consensus set
// is consistent with some rules that should always be true.
func (cs *State) checkConsistency() error {
	err := cs.checkCurrentPath()
	if err != nil {
		return err
	}
	err = cs.checkDelayedSiacoinOutputMaps()
	if err != nil {
		return err
	}

	// COMPATv0.4.0
	//
	// checkSiacoins is broken for file contracts that are created before the
	// hardfork barrier but terminate after the hardfork barrier. This break
	// will not be a problem after the hardfork checkpoint is created.
	/*
		err = cs.checkSiacoins()
		if err != nil {
			return err
		}
	*/

	err = cs.checkSiafunds()
	if err != nil {
		return err
	}
	err = cs.checkRewindApply()
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>bring back consistency check<commit_after>package consensus

import (
	"errors"
	"sort"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errSiacoinMiscount = errors.New("consensus set has the wrong number of siacoins given the height")
	errSiafundMiscount = errors.New("consensus set has the wrong number of siafunds")
)

// checkCurrentPath looks at the blocks in the current path and verifies that
// they are all ordered correctly and in the block map.
func (cs *State) checkCurrentPath() error {
	currentNode := cs.currentBlockNode()
	for i := cs.height(); i != 0; i-- {
		// The block should be in the block map.
		_, exists := cs.blockMap[currentNode.block.ID()]
		if !exists {
			return errors.New("current path block not found in block map")
		}
		// Current node should match the id in the current path.
		if currentNode.block.ID() != cs.currentPath[i] {
			return errors.New("current path points to an incorrect block")
		}
		// Height of node needs to be listed correctly.
		if currentNode.height != i {
			return errors.New("node height mismatches its location in the blockchain")
		}
		// Current node's parent needs the right id.
		if currentNode.block.ParentID != currentNode.parent.block.ID() {
			return errors.New("node parent id mismatches actual parent id")
		}

		currentNode = currentNode.parent
	}
	return nil
}

// checkDelayedSiacoinOutputMaps checks that the delayed siacoin output maps
// have the right number of maps at the right heights.
func (cs *State) checkDelayedSiacoinOutputMaps() error {
	expected := 0
	for i := cs.height() + 1; i <= cs.height()+types.MaturityDelay; i++ {
		if !(i > types.MaturityDelay) {
			continue
		}
		_, exists := cs.delayedSiacoinOutputs[i]
		if !exists {
			return errors.New("delayed siacoin outputs are in an inconsistent state")
		}
		expected++
	}
	if len(cs.delayedSiacoinOutputs) != expected {
		return errors.New("delayed siacoin outputs has too many maps")
	}

	return nil
}

// checkSiacoins counts the number of siacoins in the database and verifies
// that it matches the sum of all the coinbases.
func (cs *State) checkSiacoins() error {
	expectedSiacoins := types.ZeroCurrency
	for i := types.BlockHeight(0); i <= cs.height(); i++ {
		expectedSiacoins = expectedSiacoins.Add(types.CalculateCoinbase(i))
	}
	totalSiacoins := types.ZeroCurrency
	for _, sco := range cs.siacoinOutputs {
		totalSiacoins = totalSiacoins.Add(sco.Value)
	}
	for _, fc := range cs.fileContracts {
		var payout types.Currency
		for _, output := range fc.ValidProofOutputs {
			payout = payout.Add(output.Value)
		}
		totalSiacoins = totalSiacoins.Add(payout)
	}
	for _, dsoMap := range cs.delayedSiacoinOutputs {
		for _, dso := range dsoMap {
			totalSiacoins = totalSiacoins.Add(dso.Value)
		}
	}
	for _, sfo := range cs.siafundOutputs {
		sfoSiacoins := cs.siafundPool.Sub(sfo.ClaimStart).Div(types.SiafundCount).Mul(sfo.Value)
		totalSiacoins = totalSiacoins.Add(sfoSiacoins)
	}
	if expectedSiacoins.Cmp(totalSiacoins) != 0 {
		return errSiacoinMiscount
	}
	return nil
}

// checkSiafunds counts the siafund outputs and checks that there are 10,000.
func (cs *State) checkSiafunds() error {
	totalSiafunds := types.ZeroCurrency
	for _, sfo := range cs.siafundOutputs {
		totalSiafunds = totalSiafunds.Add(sfo.Value)
	}
	if totalSiafunds.Cmp(types.SiafundCount) != 0 {
		return errSiafundMiscount
	}
	return nil
}

// consensusSetHash returns the Merkle root of the current state of consensus.
func (cs *State) consensusSetHash() crypto.Hash {
	// Items of interest:
	// 1.	genesis block
	// 3.	current height
	// 4.	current target
	// 5.	current depth
	// 6.	current path + diffs
	// 7.	earliest allowed timestamp of next block
	// 8.	unspent siacoin outputs, sorted by id.
	// 9.	open file contracts, sorted by id.
	// 10.	unspent siafund outputs, sorted by id.
	// 11.	delayed siacoin outputs, sorted by height, then sorted by id.
	// 12.	siafund pool

	// Create a slice of hashes representing all items of interest.
	tree := crypto.NewTree()
	tree.PushObject(cs.blockRoot.block)
	tree.PushObject(cs.height())
	tree.PushObject(cs.currentBlockNode().childTarget)
	tree.PushObject(cs.currentBlockNode().depth)
	tree.PushObject(cs.currentBlockNode().earliestChildTimestamp())

	// Add all the blocks in the current path TODO: along with their diffs.
	for i := 0; i < len(cs.currentPath); i++ {
		tree.PushObject(cs.currentPath[types.BlockHeight(i)])
	}

	// Add all of the siacoin outputs, sorted by id.
	var openSiacoinOutputs crypto.HashSlice
	for siacoinOutputID, _ := range cs.siacoinOutputs {
		openSiacoinOutputs = append(openSiacoinOutputs, crypto.Hash(siacoinOutputID))
	}
	sort.Sort(openSiacoinOutputs)
	for _, id := range openSiacoinOutputs {
		sco, _ := cs.siacoinOutputs[types.SiacoinOutputID(id)]
		tree.PushObject(id)
		tree.PushObject(sco)
	}

	// Add all of the file contracts, sorted by id.
	var openFileContracts crypto.HashSlice
	for fileContractID, _ := range cs.fileContracts {
		openFileContracts = append(openFileContracts, crypto.Hash(fileContractID))
	}
	sort.Sort(openFileContracts)
	for _, id := range openFileContracts {
		// Sanity Check - file contract should exist.
		fc, _ := cs.fileContracts[types.FileContractID(id)]
		tree.PushObject(id)
		tree.PushObject(fc)
	}

	// Add all of the siafund outputs, sorted by id.
	var openSiafundOutputs crypto.HashSlice
	for siafundOutputID, _ := range cs.siafundOutputs {
		openSiafundOutputs = append(openSiafundOutputs, crypto.Hash(siafundOutputID))
	}
	sort.Sort(openSiafundOutputs)
	for _, id := range openSiafundOutputs {
		sco, _ := cs.siafundOutputs[types.SiafundOutputID(id)]
		tree.PushObject(id)
		tree.PushObject(sco)
	}

	// Get the set of delayed siacoin outputs, sorted by maturity height then
	// sorted by id and add them.
	for i := cs.height() + 1; i <= cs.height()+types.MaturityDelay; i++ {
		var delayedSiacoinOutputs crypto.HashSlice
		for id := range cs.delayedSiacoinOutputs[i] {
			delayedSiacoinOutputs = append(delayedSiacoinOutputs, crypto.Hash(id))
		}
		sort.Sort(delayedSiacoinOutputs)
		for _, delayedSiacoinOutputID := range delayedSiacoinOutputs {
			delayedSiacoinOutput, _ := cs.delayedSiacoinOutputs[i][types.SiacoinOutputID(delayedSiacoinOutputID)]
			tree.PushObject(delayedSiacoinOutput)
			tree.PushObject(delayedSiacoinOutputID)
		}
	}

	// Add the siafund pool
	tree.PushObject(cs.siafundPool)

	return tree.Root()
}

// checkRewindApply rewinds and reapplies the current block, checking that the
// consensus set hashes meet the expected values.
func (cs *State) checkRewindApply() error {
	// Do nothing if the DEBUG flag is not set.
	if !build.DEBUG {
		return nil
	}

	// Rewind a block, check that the consensus set hash after rewiding is the
	// same as it was before the current block was added, then reapply the
	// block and check that the new consensus set has is the same as originally
	// calculated.
	currentNode := cs.currentBlockNode()
	cs.revertToNode(currentNode.parent)
	if cs.consensusSetHash() != currentNode.parent.consensusSetHash {
		return errors.New("rewinding a block resulted in unexpected consensus set hash")
	}
	cs.applyUntilNode(currentNode)
	if cs.consensusSetHash() != currentNode.consensusSetHash {
		return errors.New("reapplying a block resulted in unexpected consensus set hash")
	}
	return nil
}

// checkConsistency runs a series of checks to make sure that the consensus set
// is consistent with some rules that should always be true.
func (cs *State) checkConsistency() error {
	err := cs.checkCurrentPath()
	if err != nil {
		return err
	}
	err = cs.checkDelayedSiacoinOutputMaps()
	if err != nil {
		return err
	}
	err = cs.checkSiacoins()
	if err != nil {
		return err
	}
	err = cs.checkSiafunds()
	if err != nil {
		return err
	}
	err = cs.checkRewindApply()
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>