-
Notifications
You must be signed in to change notification settings - Fork 499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
services/horizon: Use new ingestion data in /accounts/{account_id}
#1868
Changes from all commits
0358cef
62a083e
30dd4b5
9c4a84b
22a04eb
88bef55
1742a8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,20 +2,23 @@ package actions | |
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
|
||
protocol "github.com/stellar/go/protocols/horizon" | ||
"github.com/stellar/go/services/horizon/internal/db2/core" | ||
"github.com/stellar/go/services/horizon/internal/db2/history" | ||
"github.com/stellar/go/services/horizon/internal/resourceadapter" | ||
"github.com/stellar/go/support/errors" | ||
"github.com/stellar/go/support/log" | ||
"github.com/stellar/go/support/render/hal" | ||
"github.com/stellar/go/support/render/problem" | ||
"github.com/stellar/go/xdr" | ||
) | ||
|
||
// AccountInfo returns the information about an account identified by addr. | ||
func AccountInfo(ctx context.Context, cq *core.Q, addr string) (*protocol.Account, error) { | ||
func AccountInfo(ctx context.Context, cq *core.Q, hq *history.Q, addr string, enableExperimentalIngestion bool) (*protocol.Account, error) { | ||
var ( | ||
coreRecord core.Account | ||
coreData []core.AccountData | ||
|
@@ -52,8 +55,200 @@ func AccountInfo(ctx context.Context, cq *core.Q, addr string) (*protocol.Accoun | |
coreSigners, | ||
coreTrustlines, | ||
) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "populating account") | ||
} | ||
|
||
if enableExperimentalIngestion { | ||
c, err := json.Marshal(resource) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "error marshaling resource") | ||
} | ||
|
||
// We send JSON bytes to compareAccountResults to prevent modifying | ||
// `resource` in any way. | ||
err = compareAccountResults(ctx, hq, c, addr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we check that both ingestion systems are at the same ledger before comparing results? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do check this using |
||
if err != nil { | ||
log.Ctx(ctx).WithFields(log.F{ | ||
"err": err, | ||
"accounts_check": true, // So it's easy to find all diffs | ||
}).Warn("error comparing core and horizon accounts") | ||
} | ||
} | ||
|
||
return &resource, nil | ||
} | ||
|
||
func compareAccountResults( | ||
ctx context.Context, | ||
hq *history.Q, | ||
expectedResourceBytes []byte, | ||
addr string, | ||
) error { | ||
var ( | ||
horizonRecord history.AccountEntry | ||
horizonData []history.Data | ||
horizonSigners []history.AccountSigner | ||
horizonTrustLines []history.TrustLine | ||
newResource protocol.Account | ||
) | ||
|
||
horizonRecord, err := hq.GetAccountByID(addr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
horizonData, err = hq.GetAccountDataByAccountID(addr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return &resource, errors.Wrap(err, "populating account") | ||
horizonSigners, err = hq.GetAccountSignersByAccountID(addr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
horizonTrustLines, err = hq.GetTrustLinesByAccountID(addr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = resourceadapter.PopulateAccountEntry( | ||
ctx, | ||
&newResource, | ||
horizonRecord, | ||
horizonData, | ||
horizonSigners, | ||
horizonTrustLines, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var expectedResource protocol.Account | ||
err = json.Unmarshal(expectedResourceBytes, &expectedResource) | ||
if err != nil { | ||
return errors.Wrap(err, "Error unmarshaling expectedResourceBytes") | ||
} | ||
|
||
if err = accountResourcesEqual(newResource, expectedResource); err != nil { | ||
return errors.Wrap( | ||
err, | ||
fmt.Sprintf( | ||
"Core and Horizon accounts responses do not match: %+v %+v", | ||
expectedResource, newResource, | ||
)) | ||
|
||
} | ||
|
||
return nil | ||
} | ||
|
||
// accountResourcesEqual compares two protocol.Account objects and returns an | ||
// error if they are different but only if `LastModifiedLedger` fields are the | ||
// same. | ||
func accountResourcesEqual(actual, expected protocol.Account) error { | ||
if actual.Links != expected.Links { | ||
return errors.New("Links are different") | ||
} | ||
|
||
if actual.LastModifiedLedger != expected.LastModifiedLedger { | ||
// Modified at different ledgers so values will be different | ||
return nil | ||
} | ||
|
||
if actual.ID != expected.ID || | ||
actual.AccountID != expected.AccountID || | ||
actual.Sequence != expected.Sequence || | ||
actual.SubentryCount != expected.SubentryCount || | ||
actual.InflationDestination != expected.InflationDestination || | ||
actual.HomeDomain != expected.HomeDomain || | ||
actual.Thresholds != expected.Thresholds || | ||
actual.Flags != expected.Flags { | ||
return errors.New("Main fields are different") | ||
} | ||
|
||
// Ignore PT | ||
|
||
// Balances | ||
balances := map[string]protocol.Balance{} | ||
for _, balance := range expected.Balances { | ||
id := balance.Asset.Type + balance.Asset.Code + balance.Asset.Issuer | ||
balances[id] = balance | ||
} | ||
|
||
for _, actualBalance := range actual.Balances { | ||
id := actualBalance.Asset.Type + actualBalance.Asset.Code + actualBalance.Asset.Issuer | ||
expectedBalance := balances[id] | ||
delete(balances, id) | ||
|
||
if expectedBalance.LastModifiedLedger != actualBalance.LastModifiedLedger { | ||
// Modified at different ledgers so values will be different | ||
continue | ||
} | ||
|
||
if expectedBalance.Balance != actualBalance.Balance || | ||
expectedBalance.Limit != actualBalance.Limit || | ||
expectedBalance.BuyingLiabilities != actualBalance.BuyingLiabilities || | ||
expectedBalance.SellingLiabilities != actualBalance.SellingLiabilities { | ||
return errors.New("Balance " + id + " is different") | ||
} | ||
|
||
if expectedBalance.IsAuthorized == nil && actualBalance.IsAuthorized == nil { | ||
continue | ||
} | ||
|
||
if expectedBalance.IsAuthorized != nil && actualBalance.IsAuthorized != nil && | ||
*expectedBalance.IsAuthorized == *actualBalance.IsAuthorized { | ||
continue | ||
} | ||
|
||
return errors.New("IsAuthorized is different for " + id) | ||
} | ||
|
||
if len(balances) > 0 { | ||
return errors.New("Some extra balances") | ||
} | ||
|
||
// Signers | ||
signers := map[string]protocol.Signer{} | ||
for _, signer := range expected.Signers { | ||
signers[signer.Key] = signer | ||
} | ||
|
||
for _, actualSigner := range actual.Signers { | ||
expectedSigner := signers[actualSigner.Key] | ||
delete(signers, actualSigner.Key) | ||
|
||
if expectedSigner != actualSigner { | ||
return errors.New("Signer is different") | ||
} | ||
} | ||
|
||
if len(signers) > 0 { | ||
return errors.New("Extra signers") | ||
} | ||
|
||
// Data | ||
data := map[string]string{} | ||
for key, value := range expected.Data { | ||
data[key] = value | ||
} | ||
|
||
for actualKey, actualValue := range actual.Data { | ||
expectedValue := data[actualKey] | ||
delete(data, actualKey) | ||
|
||
if expectedValue != actualValue { | ||
return errors.New("Data is different") | ||
} | ||
} | ||
|
||
if len(data) > 0 { | ||
return errors.New("Extra data") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// AccountsQuery query struct for accounts end-point | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not pass in the resource directly to
compareAccountResults()
? you end up unmarshaling the bytes incompareAccountResults()
so you could skip that step by sending theprotocol.Account
instance tocompareAccountResults()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's explained in the comments:
It's to minimize chances of rendering data from expingest instead of stellar-core by accident. The compare code will be removed when expingest goes to production.