-
Notifications
You must be signed in to change notification settings - Fork 9
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
Add examples of ingest mapping for old OriginListing contract #2
Conversation
Another question... Do we want the mapping to define everything required to index an entity (i.e. also point to the schema, and the raw data), or will there be some third configuration/ registry that links the mapping to raw data and a schema? I'm currently leaning towards maybe having the mapping define the entire data source... |
pipeline: | ||
- filter: | ||
field: event | ||
value: NewListing |
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.
Do we need to include the event params to disambiguate between overloaded events with different parameters?
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.
Sure, "event" field was just an example, but my thinking is that we would be able to filter by any of the fields listed here:
https://github.com/ethereum/wiki/wiki/JavaScript-API#callback-return
And perhaps metadata which can be filtered on as listed here:
https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethfilter
# are referenced. | ||
definitions: | ||
# Not actual OriginListing ABI | ||
originListingAbi: &originListingAbi '[{"constant":true,"inputs":[{"name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cfoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"},{"name":"_preferredTransport","type":"string"}],"name":"tokenMetadata","outputs":[{"name":"infoUrl","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"promoCreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ceoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_STARTING_PRICE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSiringAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pregnantKitties","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isPregnant","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_AUCTION_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"siringAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setGeneScienceAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCEO","type":"address"}],"name":"setCEO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCOO","type":"address"}],"name":"setCOO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSaleAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"sireAllowedToAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"canBreedWith","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSiringAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"val","type":"uint256"}],"name":"setAutoBirthFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_sireId","type":"uint256"}],"name":"approveSiring","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCFO","type":"address"}],"name":"setCFO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"},{"name":"_owner","type":"address"}],"name":"createPromoKitty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"secs","type":"uint256"}],"name":"setSecondsPerBlock","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"newContractAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSaleAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"count","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_v2Address","type":"address"}],"name":"setNewAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"secondsPerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"name":"ownerTokens","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"}],"name":"giveBirth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAuctionBalances","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"cooldowns","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"cooAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"autoBirthFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"erc721Metadata","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"}],"name":"createGen0Auction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isReadyToBreed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PROMO_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_contractAddress","type":"address"}],"name":"setMetadataAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"saleAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getKitty","outputs":[{"name":"isGestating","type":"bool"},{"name":"isReady","type":"bool"},{"name":"cooldownIndex","type":"uint256"},{"name":"nextActionAt","type":"uint256"},{"name":"siringWithId","type":"uint256"},{"name":"birthTime","type":"uint256"},{"name":"matronId","type":"uint256"},{"name":"sireId","type":"uint256"},{"name":"generation","type":"uint256"},{"name":"genes","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sireId","type":"uint256"},{"name":"_matronId","type":"uint256"}],"name":"bidOnSiringAuction","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"gen0CreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"geneScience","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"breedWithAuto","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"cooldownEndBlock","type":"uint256"}],"name":"Pregnant","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"approved","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"kittyId","type":"uint256"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"genes","type":"uint256"}],"name":"Birth","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"}],"name":"ContractUpgrade","type":"event"}]' |
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.
I would much rather see this be an IPLD URL with the ABI stored on IPFS.
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.
I guess we'll need to decide in general where these mappings are going to be stored. If the mapping is already stored in IPFS, and then registered on Ethereum, then it makes no difference if it's one IPFS file or many that the mapping comprises. For anything that we take a dependency on IPFS for, we'll need to worry about data availability, and how the network handles one or more files required to index a data source being missing.
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.
Maybe for now, an IPFS hash would be best as a reference since the IPLD spec is still a draft/ incomplete.
8edcb4a
to
f42b3b9
Compare
# lastName: String | ||
# } | ||
schema: | ||
/: /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k |
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.
I don't think this would work with the schema registry idea
# How to access entities using smart contract interface. | ||
# Not the source of truth on current state, but used in event source logic. | ||
accessors: | ||
- entity: "@OriginProtocol/Listing" |
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.
Are the quotes necessary?
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.
They are, because of the @ symbol... might be another way to escape it though.
- fork: | ||
- filter: | ||
event: NewListing | ||
- getEntityByIndex: |
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.
Is this supposed to correspond to the getByIndex on line 32? How?
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.
The entity: @OriginProtocol/Listing
parameter indicates to use the accessor for that entity that's listed above.
# are referenced. | ||
definitions: | ||
# Links to the GraphQL schema being mapped to | ||
schema: |
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.
This shouldn't be necessary if mappings are implicitly linked to entities by being part of a data source that describes what entities it provides.
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.
Yeah this example sort of evolved from a mappings spec, to a data source spec.
schema: | ||
# An IPLD link. This may not be needed if we have a single global schema. | ||
/: /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k | ||
# A single DataSource may comprise multiple DataSets. |
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 may be beneficial to look at mapping design in isolation first, before combining it with the concept of data sets and data sources.
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.
I had started with pure mappings, but understanding the information it would be combined with felt like important context to take into account.
/: /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k | ||
# A single DataSource may comprise multiple DataSets. | ||
# DataSets are composed of Data, Structure & Mapping, which are intentionally | ||
# decoupled. |
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.
The way this is done here may not work. I feel "mapping" and "structure" are inherently linked. The challenge of keeping them separate is to find a way to define the mapping in way that is agnostic of the structure. So e.g. you'd have to describe the mapping for an Ethereum smart contract in a way that is completely unaware of smart contracts. If we can make that happen, it may be useful, but it may not be worth it or necessary.
Looking at the mapping in this file, you can already see that structure
and mapping
are not truly independent: both know it's a smart contract.
I also think data
should not even exist here—unless this were a data source specification. But data sources are be managed in the data source registry and are unlikely to be defined in YAML.
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.
That's a good point on the structure vs. mappings, I went back and forth on that a few times myself. Part of my motivation for separating was reuse, for example if you have several mappings that get entities from the same data, do we want to redefine the structure each time in the spec?
As I mentioned above, after my earlier commits this did sort of evolve into a data source spec, so I apologize for the confusion there.
I do think there is value in having a data source spec that is in YAML, and that doesn't require a running Ethereum blockchain with an instance of a data source registry deployed. If the users of The Graph are querying data from IPFS, in a local network or trusted node setup (not requiring exchange of tokens), it would be a shame to require an entire running Ethereum blockchain just to store the data source.
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.
Ok, I can buy the idea of a data source registry that points to a file on IPFS for the actual spec of the data source. As long as the data source can be curated and voted on at the right degree of granularity.
- entity: "@OriginProtocol/Listing" | ||
# getListing is a public method on the OriginProtocol Listing contract | ||
# for accessing the Listing entity | ||
getByIndex: getListing |
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.
There is a problem with this approach: it does not describe how a listing entity is assembled from the result of this getter. Listings are a combination of a Listing
struct and a JSON file on IPFS. An indexer needs to know that and it needs to know how these two pieces are combined into a ForSaleListing
, HousingListing
etc. entity.
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.
So we actually have at least two problems to solve here:
- How does the result of
getListing
map to an entity (including all its fields)? - What is the type of this entity (e.g.
ForSaleListing
,HousingListing
)?
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.
This example didn't account for IPFS, and was only trying to account for what an Ethereum or IPFS mappings might look like separately, but these are good points.
# Gets the state *after* the Listing was updated. | ||
- getEntityByIndex: | ||
entity: "@OriginProtocol/Listing" | ||
index: _index |
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.
Comment removed, I was wrong there.
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.
Also, @OriginProtocol/Listing
is not a type. It's an interface. What's being updated in response to an event is a specific type of listing that needs to be determined based on the fields of the listing.
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.
Right, didn't deal with traversing the IPFS relationship w/ this example, but you are correct
I see a bunch of problems with this proposal. I've taken some time to try to come up with an alternative that solely focuses on the mapping aspect, based on my experience with indexing the Origin listings. I'll post it here for inspiration. It may not cover everything yet (e.g. fields may require optional pipelines to transform values) and it may be a little too specific in some places (e.g. sometimes the It addresses an important issue though: indexers need to know what to get out from data sources and how. That's what we aim to solve with mappings. Note: I think versioning at the mapping spec level is good but we may need to version parts of the mappings too in order to be able to uprade them independently. E.g. the spec for each mapping kind should have its own version and the top-level mapping spec should only specify the minimum we need to be able to use arbitrary mapping kinds. In short: modularity is what we are after. Another note: I think it's absolutely crucial that we evaluate any mapping design we come up with against CryptoKitties as well. The case studies demo document I wrote a while ago covers how the CK contracts are structured and they are significantly more complex than Origin listings. specVersion: mapping/v1
mappings:
- kind: ethereum/array-collection
entity: ForSaleListing
# Collection name and size define the state variables or
# functions that provide the array collection and its length
collectionName: listings
collectionSize: listingsLength
# Fields defines fields that can be retrieved from the
# structs returned for each item in the array collection;
#
# Fields starting with an underscore (e.g. _ipfsHash) are
# considered internal and are stripped of before passing
# entities on to the indexer's data source event handler
fields:
lister:
kind: ethereum/struct-field-by-index
index: 0
_ipfsHash:
kind: ethereum/struct-field-by-index
index: 1
price:
kind: ethereum/struct-field-by-index
index: 2
unitsAvailable:
kind: ethereum/struct-field-by-index
index: 3
# Extra fields are additional fields that can come from additional
# smart contract function calls, external files e.g. on IPFS and more;
# they can refer to values of regular fields
extraFields:
id:
kind: ethereum/array-collection-item-index
_schema:
kind: ipfs/field-in-json-file
hash:
source: field
name: _ipfsHash
path: ["data", "schema"]
category:
kind: ipfs/field-in-json-file
hash:
source: field
name: _ipfsHash
path: ["data", "category"]
description:
kind: ipfs/field-in-json-file
hash:
source: field
name: _ipfsHash
path: ["data", "description"]
# Transformations are applied to entities after all of their
# fields have been collected; in this example, these items
# are filtered based on a filter called is-for-sale-listing, which
# is defined further below
transformations:
- kind: filter
name: is-for-sale-listing
# Events describe events that can lead to entities being reindexed
events:
- name: NewListing
pipeline:
- kind: ethereum/event-param
name: _index
- kind: ethereum/array-collection-item
- kind: filter
name: is-for-sale-listing
- kind: update-entity
- name: ListingPurchased
pipeline:
- kind: ethereum/event-param
name: _index
- kind: ethereum/array-collection-item
- kind: filter
name: is-for-sale-listing
- kind: update-entity
# Filters can be used in transformations and events and are defined
# in a single place
filters:
is-for-sale-listing:
kind: filter-by-field-value
field: _schema
value: https://localhost:3000/schemas/for-sale.json
- kind: ethereum/array-collection
entity: AnnouncementListing
collectionName: listings
collectionSize: listingsLength
fields:
lister:
kind: ethereum/struct-field-by-index
index: 0
_ipfsHash:
kind: ethereum/struct-field-by-index
index: 1
price:
kind: ethereum/struct-field-by-index
index: 2
unitsAvailable:
kind: ethereum/struct-field-by-index
index: 3
extraFields:
id:
kind: ethereum/array-collection-item-index
_schema:
kind: ipfs/field-in-json-file
hash:
source: field
name: _ipfsHash
path: ["data", "schema"]
category:
kind: ipfs/field-in-json-file
hash:
source: field
name: _ipfsHash
path: ["data", "category"]
description:
kind: ipfs/field-in-json-file
hash:
source: field
name: _ipfsHash
path: ["data", "description"]
transformations:
- kind: filter
name: filter-announcement
events:
- name: NewListing
pipeline:
- kind: ethereum/event-param
name: _index
- kind: ethereum/array-collection-item
- kind: filter
name: is-announcement-listing
- kind: update-entity
- name: ListingPurchased
pipeline:
- kind: ethereum/event-param
name: _index
- kind: ethereum/array-collection-item
- kind: filter
name: is-announcement-listing
- kind: update-entity
filters:
is-announcement-listing:
kind: filter-by-field-value
field: _schema
value: https://localhost:3000/schemas/announcement.json |
f42b3b9
to
0ba451a
Compare
Okay @yanivtal @Jannis , PR has been updated and is ready for another look. What's changed:
The idea here is to explore the three possible approaches:
These approaches can be used in combination with one another, though we'd likely want to settle on either a generic DSL or specialized DSL. The Javascript mapping that I wrote, made some simplifying assumptions, primarily that we can switch on the Overall I think the Javascript approach turned out OK. It's simple enough to reason about for me at least, and could probably be made even cleaner using |
0ba451a
to
a6edb7a
Compare
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.
Great start. I'd like to see this made a little more ergonomic. I agree async/await will help (should we do that now or on a subsequent PR?)
One thing that could be nice is using 0X's smart contract -> TypeScript type generator. It would be awesome if the values you got out of web3 were typed.
export const load = (entity, loadEntity) => { | ||
switch(entity.category) { | ||
case "ForSale": | ||
createEntity('ForSaleListing', entity) |
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.
Where's this function defined?
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.
Should be loadEntity
. Missed this in refactor.
* @param {[type]} ipfs A client for interacting with an IPFS storage backend. | ||
* @param {[type]} triggerUpdate A function to call anytime reindexing should occur. | ||
*/ | ||
export const update = ({ ipfs, web3 }, triggerUpdate) => { |
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.
Where's this used?
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.
All of these exported functions would be consumed as part of the module, presumably.
Another thing I forgot to mention on the PR: This approach would suffer from memory problems if the data in the contract was sufficiently large (unlikely to happen on Ethereum), or if the input data was a large data set on IPFS. For large datasets we could use a NodeJS |
I don't think this should be stuck in a PR. Looks like a promising start. |
I have to say, I like the JS-based mappings. Definitely use async/await though. |
Another drawback of the current |
@Jannis A process question for you: I'm happy to merge this PR, but I'm concerned about losing the context of the conversation we're having here and merging something that still hasn't solved all the problems we've identified for the mappings. Would you prefer to keep this PR alive as we evolve and explore different designs, until we reach a satisfactory v0.1, or would you prefer to get this merged and I can track the problems with the current approaches as Github Issues in this repo? |
Submitting WIP PR for early feedback. I initially started with the spec, but I think the better approach for me is to work through several examples to get a broad feel for what the surface area of the API will be.
Comments in the example YAML file describe how those parts of the config might work.
Questions for feedback: