Skip to content
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

Improved deals & retrieval record tracking & new indices and records API & support verified deals #772

Merged
merged 30 commits into from
Feb 19, 2021

Conversation

jsign
Copy link
Contributor

@jsign jsign commented Feb 10, 2021

This PR includes new features and improvements to Powergate.

Lots of things happened here. To improve visibility and review having a mental map of changes, I'll describe the big-picture of changes in different sections.

Most of these changes are motivated by this Textile PR


We add verified-deals support in StorageConfigs. A new attribute VerifiedDeal indicates that new deals will be marked as verified in the deal proposal. Of course, the wallet address making the deal should be a verified-client and have enough data-cap to create the verified-deal.

When a verified-deal deal is done, all existing miner-selectors use the verified-price from miners instead of the usual price for unverified data. The semantics of MaxPrice still hold, being verified or unverified, it will block prices that are above MaxPrice.


A new group of APIs was created to expose indices data for miners.
These APIs are:

  • GetMiners: which allows getting a list of all miner-addresses (and no more information) that satisfy some filters (e.g: have power >0 in the network).
  • GetMinersInfo: Provided with a list of miner-addresses, it will return all known information from indices from these miners, such as ask-price, verified-ask price, relative power, sector-size, min/max piece size, etc.

If an external application wants to update information about miners, it can:

  1. Call GetMiners to get a list of miner-address of all known miners (this output won't be big since only their address is included).
  2. It can batch miners calling GetMinersInfo to avoid making big queries. Say, fetching in batches of 30 or 40 miners.

Also, my intention is to let GetMinersInfo to include information from the miners index, ask index, or any other internal index. I think this is good to simply have in a single API call consolidated information about the miner. Doing more granular APIs for the different indices doesn't sound like something needed, and also explodes the API surface.


A new group of APIs was created to expose deals record fetching, as to understand made deals and retrievals executed by Powergate.

The idea behind these APIs is that an application can fetch incrementally new records that are created or updated with new information:

  • GetUpdatedRetrievalRecordsSince(ctx context.Context, sinceNano int64, limit int)
  • GetUpdatedStorageDealRecordsSince(ctx context.Context, sinceNano int64, limit int)

With these APIs, the application can fetch which records changed since some last known sinceNano time, and get the updated information. It can also have some safety-bounds using limit to avoid very big outputs if there's too much data to be discovered. If that's the case, it can continue asking for the next set of updated records in a batching mode (sinceNano + limit).

The way this is implemented is every time a record is created or touched, it's included in an updated-index.

This change needed to create a migration to allow existing records to be discovered by these new APIs. If no migration is done, querying for the old records would result in zero results. To allow all (old and new) records to be discovered by the same new system, I created a migration that sets the update-index modified timestamp to the existing records' timestamp. This allows you not to have special cases where you can't rely on the updated-index or having to create "GetAllRecords" style APIs to get old-things or similar.


I took advantage and refactored the deals module in mulitiple files, and simplified the Watch API.

Also, the store of records now it's in its own package with clearly exported methods. Also, the API of this store was simplified.


Before this PR, if a deal failed, it's the corresponding deal-storage record (Pending: true ) was deleted. That's not the case now. A new field Failed is included, and if the deal fails (or timeouts), it would be set to true.

This allows to not lose information about attempted deals that failed. Any attempt to make a deal will be recorded.


Retrieval records now also include their ID as an attribute. This also needed some work as a migration since existing records didn't have an ID value.

The main argument for doing this is because when clients receive retrieval records since their values don't have an ID, it is tough to merge that data with existing data the client has fetched before. Saying it differently, if you don't have an ID... how you "join" to update?

Of course, we could ask the client to do the MD5 of the concatenated 4 values we use internally... but we are not evil with our clients. :)


The Ask index now also includes the Verified Ask Price. Before, we only stored the general storage price, but miners also advertise the price if you're making a verified deal.

@jsign jsign self-assigned this Feb 10, 2021
@jsign jsign changed the title Improved deals & retrieval record tracking & new indices API Improved deals & retrieval record tracking & new indices and records API Feb 10, 2021
@jsign jsign changed the title Improved deals & retrieval record tracking & new indices and records API Improved deals & retrieval record tracking & new indices and records API & support verified deals Feb 11, 2021
@jsign jsign requested a review from asutula February 11, 2021 18:53
api/client/deals.go Show resolved Hide resolved
api/server/admin/indices.go Show resolved Hide resolved
api/server/admin/indices.go Show resolved Hide resolved
api/server/user/util.go Show resolved Hide resolved
api/server/util/util.go Outdated Show resolved Hide resolved
deals/module/deals.go Show resolved Hide resolved
ffs/minerselector/reptop/reptop.go Show resolved Hide resolved
ffs/minerselector/sr2/sr2.go Show resolved Hide resolved
gateway/gateway.go Show resolved Hide resolved
migration/testdata/v1_Trackstore.post Show resolved Hide resolved
@jsign jsign marked this pull request as ready for review February 11, 2021 18:53
Copy link
Member

@asutula asutula left a comment

Choose a reason for hiding this comment

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

This all looks technically very very good. Some great new features an improvements. I left a handful of comments and questions, but only one think to really consider before merging. Other thing I noticed is there's no updates to the cli, just wondering if that was an omission or coming in the future?

api/client/admin/options.go Show resolved Hide resolved
api/server/admin/indices.go Show resolved Hide resolved
api/server/util/util.go Outdated Show resolved Hide resolved
api/server/util/util.go Outdated Show resolved Hide resolved
deals/module/records.go Show resolved Hide resolved
deals/module/store/store.go Show resolved Hide resolved
deals/module/store/store.go Show resolved Hide resolved
deals/module/store/store.go Show resolved Hide resolved
migration/testdata/v1_Trackstore.post Show resolved Hide resolved
proto/powergate/user/v1/user.proto Outdated Show resolved Hide resolved
@jsign
Copy link
Contributor Author

jsign commented Feb 13, 2021

Other thing I noticed is there's no updates to the cli, just wondering if that was an omission or coming in the future?

I kept up to date the existing commands with some new flag that I added. Regarding new APIs, yes, they don't have CLI commands and that won't happen in this PR.

At the same time of doing this PR, I was also working in the textile one; and the scope of the epic was too big to consider doing a CLI in a mindful way.

I think your point is part of a bigger topic. At many places, we call "having a CLI command" just printing the raw proto as a JSON output. At some other places, we experimented using other libraries that are more interactive or similar. I could add those CLI commands easily, but I'm not totally convinced that's calling it a day in UX.

In summary, I'd say that this falls into the "coming in the future", but I feel that we should decide if the CLI should have some minimum bar greater than printing a JSON. Not strong thoughts now, but I feel we went back and forth a bit before on that before.

jsign added 18 commits February 18, 2021 11:54
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
FROM golang:1.15.5-buster as builder
FROM golang:1.16-buster as builder
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bumping Go base-image.

Comment on lines +171 to +174
DataTransferStart: timestamppb.New(time.Unix(r.DataTransferStart, 0)),
DataTransferEnd: timestamppb.New(time.Unix(r.DataTransferEnd, 0)),
SealingStart: timestamppb.New(time.Unix(r.SealingStart, 0)),
SealingEnd: timestamppb.New(time.Unix(r.SealingEnd, 0)),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using @asutula suggestion.

SealingStart: timestamppb.New(time.Unix(r.SealingStart, 0)),
SealingEnd: timestamppb.New(time.Unix(r.SealingEnd, 0)),
ErrMsg: r.ErrMsg,
UpdatedAt: timestamppb.New(time.Unix(0, r.UpdatedAt)),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, UpdatedAt has nano-granularity.

Comment on lines +82 to +124
// The index stores at the nano precision.
lock.Lock()
updatedAtTime := time.Unix(rec.Time, nanoCounter).UnixNano()
nanoCounter++
lock.Unlock()

var indexKey datastore.Key
if strings.HasPrefix(r.Key, "/deals/storage-") {
indexKey = datastore.NewKey("/deals/updatedatidx/storage").ChildString(strconv.FormatInt(updatedAtTime, 10))
var sdr deals.StorageDealRecord
if err := json.Unmarshal(r.Value, &sdr); err != nil {
lock.Lock()
errors = append(errors, fmt.Sprintf("unmarshaling storage deal record: %s", r.Key))
lock.Unlock()
return
}
sdr.UpdatedAt = updatedAtTime
buf, err := json.Marshal(sdr)
if err != nil {
lock.Lock()
errors = append(errors, fmt.Sprintf("marshaling storage deal record: %s", r.Key))
lock.Unlock()
return
}
r.Value = buf
} else if strings.HasPrefix(r.Key, "/deals/retrieval") {
indexKey = datastore.NewKey("/deals/updatedatidx/retrieval").ChildString(strconv.FormatInt(updatedAtTime, 10))
var rr deals.RetrievalDealRecord
if err := json.Unmarshal(r.Value, &rr); err != nil {
lock.Lock()
errors = append(errors, fmt.Sprintf("unmarshaling retrieval record: %s", r.Key))
lock.Unlock()
return
}
rr.UpdatedAt = updatedAtTime
buf, err := json.Marshal(rr)
if err != nil {
lock.Lock()
errors = append(errors, fmt.Sprintf("marshaling retrieval record: %s", r.Key))
lock.Unlock()
return
}
r.Value = buf
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, what changed here?
Recall that in this migration we're populating the new /deals/updatedatidx for existing records. The format of the key is /deals/updatedatidx/<updated-at-nano> that it's usually the value of the field UpdatedAt.

After doing some migration tests in our internal stack, I discovered two things to correct:

  • For old records, I used Time as the <updated-at-nano> since it's the only time that exists in these records (UpdatedAt is a new field). But, a subtle thing happens here. Time has seconds granularity, and in some cases it had collisions. I did some quick trick, and had a counter to use as the nano-part... so even if same things exists for the same second, they won't collide in the index. In new records this shouldn't happen, since UpdateAt has nano precision.
  • As things were, old records would have the UpdatedAt new field set to 0 (as being the default value). So I set those values as to have some coherency.

Maybe the code structure seems odd, but that's mostly because we're iterating keys that might be from different types. So, in each case we do the appropriate thing and update r.Value. Below, unconditionally, the original key value is updated (thing that wasn't there before, since this migration only populated /deals/updatedatidx).

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense. Tricky stuff!

Comment on lines +141 to +146
if err := ds.Put(datastore.NewKey(r.Key), r.Value); err != nil {
lock.Lock()
errors = append(errors, fmt.Sprintf("saving updated-at field: %s", err))
lock.Unlock()
return
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the Put that didn't exist before. Is mostly updating records with populated UpdatedAt field.

Comment on lines +132 to +134
// With TrimPrefix since the Key should be relative
// to the namespace of the store, and not the full-key.
if err := ds.Put(indexKey, []byte(strings.TrimPrefix(r.Key, "/deals"))); err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This TrimPrefix logic wasn't here before. This fixes an important missed thing.
The Store of records, store in /deals/updatedatidx values the key of the record that was created/updated.
BUT, that key is scoped in the Store namespace... not the global one. Since the Store is created in a key-wrapped-namespace /deals, it's important to take that into account when populating it.

Catched this while doing some testing in our internal stack.

Comment on lines +130 to +143
message GetUpdatedStorageDealRecordsSinceRequest {
google.protobuf.Timestamp since = 1;
int32 limit = 2;
}

message GetUpdatedStorageDealRecordsSinceResponse {
repeated powergate.user.v1.StorageDealRecord records = 1;
}


message GetUpdatedRetrievalRecordsSinceRequest {
google.protobuf.Timestamp since = 1;
int32 limit = 2;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also changed GetUpdatedXXXX requests to google.protobuf.Timestamp.

Comment on lines +46 to +47
AskPrice: strconv.FormatUint(lastAsk.Price, 10),
AskVerifiedPrice: strconv.FormatUint(lastAsk.VerifiedPrice, 10),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, also switching prices to string in the API.
We still need to change things internally to avoid using uint64 too, to basically avoided the same problem.
Created #776 to track.

@jsign jsign merged commit a11edcb into develop Feb 19, 2021
@jsign jsign deleted the jsign/midx branch February 19, 2021 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants