Skip to content
This repository has been archived by the owner on Jan 22, 2020. It is now read-only.

Adds created_at to trade resource #345

Merged
merged 4 commits into from
Mar 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/github.com/stellar/horizon/actions_trade.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package horizon

import (
"errors"

"fmt"

"github.com/stellar/go/xdr"
"github.com/stellar/horizon/db2"
"github.com/stellar/horizon/db2/history"
Expand All @@ -18,6 +22,9 @@ type TradeIndexAction struct {
Buying xdr.Asset
PagingParams db2.PageQuery
Records []history.Effect
// LedgerRecords is a cache of loaded ledger data used to populate the time
// when a trade occurred.
LedgerRecords map[int32]history.Ledger
Page hal.Page
}

Expand All @@ -27,6 +34,7 @@ func (action *TradeIndexAction) JSON() {
action.EnsureHistoryFreshness,
action.loadParams,
action.loadRecords,
action.loadLedgers,
action.loadPage,
func() {
hal.Render(action.W, action.Page)
Expand Down Expand Up @@ -63,14 +71,51 @@ func (action *TradeIndexAction) loadRecords() {
action.Err = trades.Page(action.PagingParams).Select(&action.Records)
}

// loadLedgers collects the unique ledgers referenced in the loaded trades and loads the details for each.
func (action *TradeIndexAction) loadLedgers() {
if len(action.Records) == 0 {
return
}

ledgerSequences := make([]interface{}, len(action.Records))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not int32 instead of interface{}?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the underlying where call accepts []interface{} and []int32 cannot be casted to []interface{}. You'd have to manually create a populate a new slice within LedgersBySequence. Since LedgersBySequence is effectively an internal only method, I'm fine with having the leniency... you'll still get a good error message if you pass an invalid value, but any numbers and strings work as expected.


// populate the unique sequences
for i, trade := range action.Records {
ledgerSequences[i] = trade.LedgerSequence()
}

var ledgers []history.Ledger
action.Err = action.HistoryQ().LedgersBySequence(
&ledgers,
ledgerSequences...,
)
if action.Err != nil {
return
}

action.LedgerRecords = map[int32]history.Ledger{}
for _, l := range ledgers {
action.LedgerRecords[l.Sequence] = l
}
}

// loadPage populates action.Page
func (action *TradeIndexAction) loadPage() {
for _, record := range action.Records {
var res resource.Trade
action.Err = res.Populate(action.Ctx, record)

ledger, found := action.LedgerRecords[record.LedgerSequence()]
if !found {
msg := fmt.Sprintf("could not find ledger data for sequence %d", record.LedgerSequence())
action.Err = errors.New(msg)
return
}

action.Err = res.Populate(action.Ctx, record, ledger)
if action.Err != nil {
return
}

action.Page.Add(res)
}

Expand Down
14 changes: 14 additions & 0 deletions src/github.com/stellar/horizon/actions_trade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package horizon
import (
"net/url"
"testing"
"time"

"github.com/stellar/horizon/db2/history"
"github.com/stellar/horizon/resource"
)

func TestTradeActions_Index(t *testing.T) {
Expand All @@ -15,6 +19,16 @@ func TestTradeActions_Index(t *testing.T) {
ht.Assert.PageOf(1, w.Body)
}

// ensure created_at is populated correctly
records := []resource.Trade{}
ht.UnmarshalPage(w.Body, &records)

l := history.Ledger{}
hq := history.Q{Repo: ht.HorizonRepo()}
ht.Require.NoError(hq.LedgerBySequence(&l, 6))

ht.Assert.WithinDuration(l.ClosedAt, records[0].LedgerCloseTime, 1*time.Second)

// for order book
var q = make(url.Values)
q.Add("selling_asset_type", "credit_alphanum4")
Expand Down
6 changes: 6 additions & 0 deletions src/github.com/stellar/horizon/db2/history/effect.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func (r *Effect) ID() string {
return fmt.Sprintf("%019d-%010d", r.HistoryOperationID, r.Order)
}

// LedgerSequence return the ledger in which the effect occurred.
func (r *Effect) LedgerSequence() int32 {
id := toid.Parse(r.HistoryOperationID)
return id.LedgerSequence
}

// PagingToken returns a cursor for this effect
func (r *Effect) PagingToken() string {
return fmt.Sprintf("%d-%d", r.HistoryOperationID, r.Order)
Expand Down
15 changes: 15 additions & 0 deletions src/github.com/stellar/horizon/db2/history/ledger.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package history

import (
"fmt"

sq "github.com/lann/squirrel"
"github.com/stellar/go/support/errors"
"github.com/stellar/horizon/db2"
)

Expand All @@ -23,6 +26,18 @@ func (q *Q) Ledgers() *LedgersQ {
}
}

// LedgersBySequence loads the a set of ledgers identified by the sequences
// `seqs` into `dest`.
func (q *Q) LedgersBySequence(dest interface{}, seqs ...interface{}) error {
if len(seqs) == 0 {
return errors.New("no sequence arguments provided")
}
in := fmt.Sprintf("sequence IN (%s)", sq.Placeholders(len(seqs)))
sql := selectLedger.Where(in, seqs...)

return q.Select(dest, sql)
}

// Page specifies the paging constraints for the query being built by `q`.
func (q *LedgersQ) Page(page db2.PageQuery) *LedgersQ {
if q.Err != nil {
Expand Down
16 changes: 16 additions & 0 deletions src/github.com/stellar/horizon/db2/history/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,20 @@ func TestLedgerQueries(t *testing.T) {
if tt.Assert.NoError(err) {
tt.Assert.Len(ls, 3)
}

// LedgersBySequence
err = q.LedgersBySequence(&ls, 1, 2, 3)

if tt.Assert.NoError(err) {
tt.Assert.Len(ls, 3)

foundSeqs := make([]int32, len(ls))
for i := range ls {
foundSeqs[i] = ls[i].Sequence
}

tt.Assert.Contains(foundSeqs, int32(1))
tt.Assert.Contains(foundSeqs, int32(2))
tt.Assert.Contains(foundSeqs, int32(3))
}
}
25 changes: 13 additions & 12 deletions src/github.com/stellar/horizon/resource/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,19 @@ type Trade struct {
Buyer hal.Link `json:"buyer"`
} `json:"_links"`

ID string `json:"id"`
PT string `json:"paging_token"`
Seller string `json:"seller"`
SoldAmount string `json:"sold_amount"`
SoldAssetType string `json:"sold_asset_type"`
SoldAssetCode string `json:"sold_asset_code,omitempty"`
SoldAssetIssuer string `json:"sold_asset_issuer,omitempty"`
Buyer string `json:"buyer"`
BoughtAmount string `json:"bought_amount"`
BoughtAssetType string `json:"bought_asset_type"`
BoughtAssetCode string `json:"bought_asset_code,omitempty"`
BoughtAssetIssuer string `json:"bought_asset_issuer,omitempty"`
ID string `json:"id"`
PT string `json:"paging_token"`
Seller string `json:"seller"`
SoldAmount string `json:"sold_amount"`
SoldAssetType string `json:"sold_asset_type"`
SoldAssetCode string `json:"sold_asset_code,omitempty"`
SoldAssetIssuer string `json:"sold_asset_issuer,omitempty"`
Buyer string `json:"buyer"`
BoughtAmount string `json:"bought_amount"`
BoughtAssetType string `json:"bought_asset_type"`
BoughtAssetCode string `json:"bought_asset_code,omitempty"`
BoughtAssetIssuer string `json:"bought_asset_issuer,omitempty"`
LedgerCloseTime time.Time `json:"created_at"`
}

// Transaction represents a single, successful transaction
Expand Down
13 changes: 12 additions & 1 deletion src/github.com/stellar/horizon/resource/trade.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ import (
)

// Populate fills out the details
func (res *Trade) Populate(ctx context.Context, row history.Effect) (err error) {
func (res *Trade) Populate(
ctx context.Context,
row history.Effect,
ledger history.Ledger,
) (err error) {
if row.Type != history.EffectTrade {
err = errors.New("invalid effect; not a trade")
return
}

if row.LedgerSequence() != ledger.Sequence {
err = errors.New("invalid ledger; different sequence than trade")
return
}

row.UnmarshalDetails(res)
res.ID = row.PagingToken()
res.PT = row.PagingToken()
res.Buyer = row.Account
res.LedgerCloseTime = ledger.ClosedAt

lb := hal.LinkBuilder{httpx.BaseURL(ctx)}
res.Links.Self = lb.Link("/accounts", res.Seller)
Expand Down