OriginProtocol / origin-js

We've moved to a monorepo: https://github.com/OriginProtocol/origin
MIT License
81 stars 33 forks source link

🎓Fractional usage and calendaring #146

Closed micahalcorn closed 6 years ago

micahalcorn commented 6 years ago

The eventual goal is to support fractional-use DApps that will involved a calendaring function. Some ideas around this are outlined in the white paper.

Things to Consider

To Do

DanielVF commented 6 years ago

One way that is flexible, simple, and gas cheap:

We could have N times slots. Each timeslot has a start and and time in UTC seconds (unix timestamp). The listing IPFS json has an array of these slots. The listing contract has an availability (bytes or array of numbers) as a bit string. We can do cheap bitwise operations on these to check availability or mark as used.

joshfraser commented 6 years ago

I love the idea of utilizing bit masks.

A couple things to remember, we'll eventually want the ability to set variable pricing for different time slots (we've been calling them "intervals") and we'll need some way to track who the purchaser of each Interval was, not just availability (although maybe we get that already from the purchase contract?).

It may also simplify things if each Interval is a fixed length of time. That allows you to have simple rules like every 6th and 7th interval should have a higher price (say for weekend pricing).

DanielVF commented 6 years ago

Yeah, each purchase contract would contain the time it was purchased for. If we did bit masks, then the purchase contract would contain a mask for the slots purchased.

One option on the variable pricing and bit masks would be sort the slots (masks and json data) by price, and then have an array of price range structs. So slots 1-20 could be 1eth, and slots 20-30 could be 1.1eth etc, etc.

I feel that we want to keep a rule engine out of the blockchain. A simple one won't handle enough cases, and we want to avoid user submitted touring complete languages running in our contracts. This is just a feeling / gut instinct, and maybe someone could prove me wrong...

An explicit list of slots can do everything a rules engine could do, availability wise, while being much easier to reason about and understand.

I don't think we need the fixed intervals of time, since any extra overhead there is just in the json, which is approximately free.

DanielVF commented 6 years ago

Are there any changes to the purchase flow that we need for this fractional usage sprint?

nick commented 6 years ago

I found some resources that may or may not be useful for this discussion:

DanielVF commented 6 years ago

Fractional Usage

Scope, Contract/origin.js/IPFS

In Scope - Single resource per contract

Allow a Listing to support a resource that can be booked for non-overlapping amounts of time, by a single purchaser:

Not in scope - overlapping resources per contract

In Scope - Non-contigous slots

Slots do not have to be adjacent to one another. For example, someone could list a wedding venue for the 3rd, 5th, and 8th of a month, without having to list the 4th, 6th, and 7th.

In Scope - Non-aligned slots

Slots do not have to line up evenly with a particular time grid. For example an escape room could have a slot from 1:30pm-2:30pm and another from 3:00pm to 4:00pm.

In Scope - Different pricing for different slots

Each slot could have its own price. For example, someone could charge higher prices to rent a place on the weekend.

In Scope - Reserving multiple slots

A user can reserve multiple slots at a time. The slots can have different costs, and the purchase price is totaled accross all reservations. For example, you could reserve three days at a house by a lake in a single purchase.

Out of Scope - Slot reservation rules

We won't support rules about what consititutes a valid set of available slots for a reservation. For example, we would not support requiring a minimum of two weekday reservations for any weekend reservation.

It's possible we may later support a minimum number of booked slots per reservation, or a minimum number of contiguous slots.

Out of Scope - Discount rules

We won't support rules like "50% off for reserving four or more slots".

Out of Scope for now - Partial refunds

We won't support partial refunds for cancelations near the slot dates.

In Scope - Abitrary sized timeslots

Different listings should support different slot sizes. These slots sizes do not need to be tied directly to normal time units - someone could offer 45 minute sized slots. (Out of scope per josh: slots inside a single listing could be different sizes)

Scope DAPP Level:

In Sprint Scope - Listing UI for night based listings

We need to support a picker for creating night based fractional listings, AirBNB style.

We need to able to enter a minumun time that the registration must be made ahead of time.

We need to be able to give a small summary of what has been reserved to go on the My Purchases / My Sales cards (for example, "June 14 to June 17th")

We need to show full time details on the official purchase page.

Near Scope - Listing UI for day based listings

Near Scope - Listing UI for multiple slots a day based listings

Near Scope - Removing Slots

People often list resources on multiple platforms. We need to make it easy for the seller to mark slots as non-available, even when no one has purchased them.

Future Scope - iCal export by buyer from Purchase

Future Scope - iCal export by Seller from Listing

Future Scope - excel/csv import of slots from seller

Stages - the big question

What changes do we need to make to our stages?

Are we changing the the stages flow for fractional listings? IE is their an intermediate period where the seller can turn down a purchase and unreserve those slots?

Do we need to do anything for buyer or seller cancelations?

Are we doing any kind of partial payment up front, then remainder X time before the first slot?

Implimentation considerations

joshfraser commented 6 years ago

Thanks for kicking this off Daniel and enumerating the various decisions we need to make.

While different types of listings should be able to have variable interval sizes (or slots as you call them), I think we should have a fixed interval size for a specific listing. Things like being able to set a minimum number of intervals (ie. min 2 nights booking) is pretty important and easier to reason about when the intervals are a consistent size.

wanderingstan commented 6 years ago

@nick that Timely doc is interesting, though I can't find any evidence that they proposed an ERC. Or any evidence of who wrote it! Do you recall where you found it? Looks like it might be from this company: https://www.gettimely.com/about/ ?

I also came upon interval trees in my research! We have some extra "inside information" that makes our use case maybe simpler than the purely academic: we'll never insert intervals before "now", we know we'll never allow intervals to overlap, and we won't delete intervals.

Still digging into this. Fun problem.

@DanielVF where is the doc you shared in the eng call yesterday with the bitmask breakdown? I keep forgetting to bookmark it!

nick commented 6 years ago

I found the Timely Resource article here on reddit.

One idea I had that may help simplify our implementation of fractional usage is to offload the pricing and availability aspects to be off-chain, and simply have an initial booking be 'pending' until 'accepted' by the seller. This would allow very complex rules for availability and pricing without having to verify everything on-chain. This would also allow us to support buyers making offers below the official price, as users frequently do on Airbnb.

The contract could just keep an 2 arrays of iCal strings... one for available slots and one for bookings. It'd be up to the seller, or the dapp the seller is using, to check the requested slot is actually available before they go ahead and accept the listing. Perhaps we could incentivize good behavior by having the seller stake some tokens which they would lose if they accept an overlapping booking, don't respond in a timely manner, etc.

For 'instant' bookings, perhaps the seller could delegate accepting valid bookings to the bridge server or other 3rd party service, which would take on the responsibility of validating bookings against listings.

In short, I think it'd be difficult to get all the functionality we desire on-chain, perhaps we can store the 'bare bones' data on the blockchain without trying to validate availability and pricing at transaction time.

cuongdo commented 6 years ago

As @wanderingstan mentioned, we shouldn't need interval trees for non-overlapping intervals. A balanced BST is sufficient for storing those, as mentioned in the "Naive approach" section of the Wikipedia article for interval trees. However, the cost of storing all the references required for a BST on the blockchain would likely make this approach expensive.

Using (potentially sparse) bitmasks seems appealing for its space savings. I'm going to think on this more.

@nick: Thanks for the links and for sharing your thoughts. In terms of moving the availability and pricing off-chain, I wouldn't feel comfortable putting the complexity on the seller or each DApp maker. However, I wonder how much responsibility could be shifted to origin.js.

DanielVF commented 6 years ago

Unrelated to the previous conversation:

Our hard work here is all about coming up with cheaper ways of representing this data - but we don't have to worry about it being possible or not. This all childishly simple if the seller pays, for example, five cents a timeslot.

nick commented 6 years ago

Yea I think origin.js can certainly provide helper methods to ease the setup and management of fractional usage listings. My main concern is how much data we should be putting on the blockchain instead of in IPFS, and whether the contract should be responsible for validation or not.

For example, if a user submits a booking request to my listing contract to book my condo next July 13th-15th for 0.5ETH... should the contract validate that those slots are available and that the submitted price is correct? Or should we just let the seller (or some automated 3rd party) decide if they want to accept the booking manually. I believe the latter would provide us with much more flexibility as well as less contract complexity as we no longer need to represent slots and state in a way that can be validated in contract code.

We could just store the availability and pricing information in IPFS, then have a 'bytes' variable that gets appended to every time there is a new booking. For example: J1-10 A3-4 S21-24 could mean a property is booked July 1st-10th, August 3rd-4th and September 21st-24th. (this is just a super simple example off the top of my head... not suggesting we actually use that format).

Representing slots on-chain could be expensive. If I'm a restaurant wanting to represent 50 tables where slots run 5pm-10pm at 15 minute intervals and accept bookings 3 months in advance, then that's 50 tables 5 hours 4 slots per hour * 90 days = 90,000 slots. A hotel with 500 rooms taking bookings 6 months in advance is also 90,000 slots. Represented as bits would be 90,000 / 256 = 350 words... a lot of storage. I think just storing actual booked slots would reduce the storage needed, at the cost of not being able to do validation (without significant complexity) on-chain.

joshfraser commented 6 years ago

I believe it's important to have on-chain validation. We add a lot of friction if every transaction has to be manually approved. And just like you don't want to count on client-side validation in traditional web apps, we can't trust client-side validation here either (people can always go around origin.js and talk to our smart contracts directly).

A few things we can do to reduce costs:

cuongdo commented 6 years ago

For bitmasks, it's also possible to use simple forms of compression that work well with bitmaps, such as run-length encoding. Hopefully, the gas cost of compression/decompression for a well-picked algorithm is far lower than the storage cost of the uncompressed data. This would have to be prototyped and profiled.

nick commented 6 years ago

Totally agree that ideally everything would be validated on chain and that should be our ultimate goal. However, doing so will require complex data structures and validation code which may be able to handle the simplest cases but I suspect will begin to break down as we reveal more and more corner cases for different models.

I suspect in the real world (at least in the early days) booking requests will need to be manually confirmed by the seller anyway:

I think this differs from trusting client-side validation as ultimately the seller is in control of whether they accept a booking or not. There's no way for the buyer to trick the seller... the seller would just reject the booking after reviewing the request.

That being said, I don't see why we shouldn't at least try a completely on-chain implementation and see how far we get :-). We should at least include an option for sellers to confirm a request though.

p.s. one other consideration I thought of is that sellers may not want 'price paid' for past transactions traceable. If you regularly make special deals for certain clients and those prices are public, it may make negotiation difficult. Eg "I saw you rented out your place last month for $100/nt but it's currently listed for $200. Can you give me the same deal?". Or "You charged your last client $50/hr, I want that price too".

wanderingstan commented 6 years ago

I couldn't sleep last night (think I'm getting a cold 😷) but was thinking about interval time representation and linked lists. Sorry for long rambling post.

On chain datetime representation

We don't need to be hesitant about having a way to get to "real" datetime values on the blockchain. That is, even if we are storing things as just slot numbers, we can and should provide ways to translate these to real timestamps when they are returned to the dapp.

Recall that reads from the blockchain are free. And it's free to call functions, no matter how complicated, as long as they don't alter the EVM state.

So we can have functions convert our slot numbers into real datetimes using libraries like these: https://github.com/pipermerriam/ethereum-datetime https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary

I think it's important that there be no ambiguity about what the contracts represent. I.e. the interpretation of slots to date/times should not happen (only) off-chain.

Ordered Linked LIsts

In a traditional ordered linked-list, the insert() function does two things:

  1. Scan the list to find the right place to insert new node
  2. Update node pointers to actually do the insert

In our blockchain world, we can massively cut on gas costs by splitting these steps apart.

Recall that reading from the chain is free. (Also free is to call functions, no matter how complicated, as long as they don't alter the EVM state.) So we can do step 1 without paying gas.

Then we just have to have a modified insert() function that also accepts where you want to make the insert, and is able to verify that it is a valid place to make the insertion. The good news in our case is that it's trivial to verify whether a desired insertion is valid.

We then put the onus on the dapp to do the "research" about where a booking belongs in the linked list. (Possibly assisted by indexing server, though I doubt that's necesarry).

In prose:

A traditional insert call would be, "Hey Mr. Linked-list! Please insert a booking for Xavier from interval 16-18." I'm proposing the insert call to be, "Hey Mr. Linked-list! Please insert a booking for Xavier from interval 16-18, I'm sure you'll agree that there is an opening right after the existing booking for Charlie."

Examples

Bookings on number line

 1 
 2 ▲ Alice [2,4]
 3 | 
 4 ▼
 5 
 6 
 7 
 8 ▲ Bob [8,9]
 9 ▼
10 ▲ Charlie [10,12]
11 |
12 ▼
13 
14 
15 
16 
17 
18 
19 ▲ Diane [19,20]
20 ▼

As Linked list nodes

HEAD: →A

{id: A, interval: [ 2, 4], next: →B    }
{id: B, interval: [ 8, 9], next: →C    }
{id: C, interval: [10,12], next: →D    }
{id: D, interval: [19,20], next: →null }

Inserting

This is the crux:

We pass along the booking that our booking should come after. In other words, we do the "work" of figuring out where in the list it should go before calling insert.

Then insert only has to verify that it's in the right place by confirming that our new booking falls between the after booking and the current next of after.)

insert( after: C, {id: X, interval: [16,18]} )

insert confirms that our new booking X does indeed come after C and before D -- thus a valid booking!

Rough solidity pseudo-code:

function insert(node after_node, node new_node) {

  // Verify we're inserting in right place with no overlap
  require(new_node.interval.start > after_node.interval.end, 
    "New node must start after after_node's end");
  require(new_node.interval.end < after_node.next.interval.start,
    "New node must end before after_node.next's start")

  // Now do regular linked list insert stuff
  new_node.next = after_node.next
  after_node.next = new_node
}

Bookings numberline view after insert

 1 
 2 ▲ Alice [2,4]
 3 | 
 4 ▼
 5 
 6 
 7 
 8 ▲ Bob [8,9]
 9 ▼
10 ▲ Charlie [10,12]
11 |
12 ▼
13 
14 
15 
16 ▲ *Xavier [16,18]
17 |
18 ▼
19 ▲ Diane [19,20]
20 ▼

Additional concerns

Simultaneous overlapping bookings

The approach should still work cases where multiple insert()s are called within one block, such that only one of them can "win". It seems acceptable behavior that one of the bookings get rejected. This would be the same for any representation where there is a race to make a booking.

For example, if Yvonne was trying to make a booking ar roughly the same time as Xavier (i.e. so that their insert() calls happened in the same block) , but her booking overlaps with his.

insert( after: C, {id: Y, interval: [15,17]} )

In this case, it would simply be up to the miner as to who's booking "wins". I don't think we can do much about that.

Simultaneous non-overlapping bookings

The simple approach would need a little more inteligence to deal with cases where multiple non-overlapping insert()s are called within one block.

Imagine that Zane is trying to make a booking ar roughly the same time as Xavier, such that they're in the same block:

insert( after: C, {id: Z, interval: [14,15]} )

If Zane's insert is executed by the miner first, then Xavier's insert will fail: the insert() function will see Zane as being the next booking after Charlie, and there is no room for Xavier's booking there!

Possible solutions: if the "blocking" booking ends before our new booking begins, treat that one as our next and repeat. I.e. the code should see that Zane's booking ends before Xavier's begins, so it's okay to look a little further.

Note that this essentially means that the insert() code is thus beginning to do a traditional list scan--the thing we wanted to avoid. I think that this is okay: its up to the submitted to determin how much gas they're willing to pay to cover these rare situations. Additonally, we could limit this scan to only scan bookings that were made in the current block (so it doesn't somehow runaway and start scannig whole list.)

Tracking "Now"

HEAD points to the start of the linked list. For our purposes, it would be useful to also maintain a pointer to the first booking which is not in the past. Call it CURRENT. This will save future callers from having to start at the beginning of the list every time they're scanning.

This could be accomplished by requiring an insert() caller to also pass along the node that is either (a) currently in progress (ie we're in middle of a booking) or (b) the node that is most recently in the past. (ie "now" lies in the gap between this old booking and the next of this node.)

To allow for inaccuracies in blockchain's version of now, this could be softened to require only +/- 10 minutes or whatever.

DanielVF commented 6 years ago

One possible rule based storage system:

image

These can also have times on them - for example a rule could be for 2pm on certain days.

I think these are the minimum needed slot functions for the listing contract:

Feel free to suggest better name changes.

We don't need to record in the listing contract who bought what - this can be handled from looking over the purchase contracts.

cuongdo commented 6 years ago

I like @DanielVF's proposal for calendar events for which the resource is either available or unavailable. However, it doesn't address the events/ticketing scenario. Specifically, each event is both scheduled and has a fixed quantity of tickets. So, we need more bits somewhere to represent that.

The other case not addressed is the freelancing scenario where a service needs to be done within a certain number of days. It's likely that a freelancer / contractor would be able to take on multiple jobs in any given period. Unfortunately, this means there's overlap in a freelancer's schedule, which hasn't been considered in-scope for this effort.

I'm continuing to think on this.

wanderingstan commented 6 years ago

@cuongdo, the events/ticketing scenario doesn't seem to actually be a case of fractional usage, I think. That is, there is no "thing" that people use/possess at different times.

Having 200 tickets for sale to a Taylor Swift concert seems more akin to having 200 widgets for sale on eBay, than to an Airbnb listing. (With obvious difference that tickets are only worth anything up until the event, and the possibility of different pricing based on seat location, time until show, etc)

Edit: I agree the freelancing scenario, while closer, also seems to not be a case of fractional usage. While it's true the freelancer has only so much time to give, it's up to them to not overbook themselves.

cuongdo commented 6 years ago

@wanderingstan You're right that events & ticketing aren't fractional usage. At the same time, as mentioned in the fractional usage/calendaring scenarios doc (search for "event dates"), there is the potential for ticketed events to recur. However, it's possible to push recurrence of ticketed events into a different layer of the stack to keep things simpler for fractional usage.

Edit: Also, I believe you're right about the freelancing scenario. I wanted to point out that scenario explicitly, since it's mentioned in the "fractional usage / calendering scenario" folder. So, then it makes sense to add some "within X days" kind of field to the "services" listing type.

wanderingstan commented 6 years ago

In effort to get clear in my own head, I did a first draft of mapping out a "Taxonomy of Listings" to better delineate the major listing types that we may support: https://docs.google.com/document/d/1RH-bEnUp7EmhtFOUHc5pofvqbVsS8pnmS4_ZpH0SsKY/edit#

@cuongdo has made the good point that we need to get clear on whats in/out of scope, and I think it'd be good to get things in context of a larger roadmap.

coleman415 commented 6 years ago

Quick note for everyone, the fractional usage/calendaring scenarios doc comparing existing/traditional sharing economy products referenced by @cuongdo has been been broken up into different docs for each use case (e.g. homesharing, freelancing, ticketing). You can find them in this folder.

Please let me know if there are changes you want made to the format/content of these docs.

ambertch commented 6 years ago

@wanderingstan mentioned something about 'what data is required for arbitration scenarios' - I know that with TrueBit state channels, the idea is to have a verification game in which enough data is available on-chain to allow anyone to verify an off-chain computation that was performed (also reference something like the Lightning Network, where the parties have to agree in order to commit transaction results back to the main chain).

In this case, the data that is required to verify a calculation might be inputs/outputs, plus a set of rules that the application logic should follow.

This makes life more complex for dapp developers, since what it amounts to is telling them "you can implement certain pieces of the application logic offchain, but in addition to posting the results back to the chain you also need to use a definition scheme/language and define a set of rules that can be used to validate whatever you do offchain" although it has the advantage, as I think @nick alludes to, that smart contract logic can be kept more general and have less complex data structures / validation code.

ambertch commented 6 years ago

As an example, in addition to weekend pricing Airbnb also supports weekly and monthly rates:

screenshot 2018-06-06 13 41 06 screenshot 2018-06-06 13 42 42

In this case, if all of the application logic were contained in the smart contract, there would need to be params for flags corresponding to whether there is X, Y, Z type discounts, as well as the multiplier of the discount. What if instead of a percentage, the discount should be a flat rate?

If a dapp developer does the pricing calculations, they might write serialized key-value pairs that define a ruleset for validation, ex: { weekendMultiplier: 1.2, priceIncreaseDays: { <timestampThanksgiving>: 50, <timestampChristmas>: 60, priceDescreaseDays: { <timestampWhenZombiesInvadeTown: 99.99 } }, weeklyDiscountPercentage: 20}

For all validation logic existing on chain, I wonder if there is a pattern analogous to pluggable middleware, like Express middleware in node.js. That way contracts containing core application logic don't need to be upgraded whenever new business requirements are discovered (although versioning and release notes address that as well).

nick commented 6 years ago

The way I see it, validation with no confirmation step is all or nothing... anything that requires validation from the seller renders validation on-chain unnecessary. If the seller needs to confirm any detail of the transaction, they may as well validate and confirm everything else off-chain too.

I think it'll be difficult to convince Airbnb users to use Origin unless we support the same functionality as Airbnb. Validating everything on-chain (just for the Airbnb case) would require checking:

User credentials

Availability

Price

All of these variables would have to be represented in a smart contract, and the validation function would need to come in under the gas limit. And this is just for Airbnb... all the other use cases probably have their own special requirements we'd also need to consider.

Instead of doing availability and price validation on-chain, we can do so in origin-js instead. The smart contract would then only be responsible for validating user credentials and storing a list of active bookings, resulting in much simpler contract code that would work for all the use cases we have outlined.

Doing price and availability validation off-chain will also make writing custom rules easier for partners - which I'm sure will be an important factor for them in deciding whether Origin is a good fit for them or not.

Although doing price and availability validation on-chain would be ideal, I think at this stage it's too big of an undertaking given the myriad of other challenges and timing issues we face. Writing all the price/availability validation off-chain in origin-js will probably take several weeks, if not a month... trying to do it all on-chain and get it right plus all the auditing of complex pricing functions will likely take a lot longer.

cuongdo commented 6 years ago

@nick Could you elaborate on your suggestion? Specifically, if enforcing availability and price is done by origin-js instead of on-chain, what on-chain checks would occur to prevent bad/invalid bookings from being made by code that makes contract calls without origin-js? If the listing allows automatic booking without seller approval, what prevents a bad actor from booking with a price that's too low for the given dates if the contract code can't validate the price?

nick commented 6 years ago

There would be nothing to stop bad/invalid bookings from being made at the contract level. However I would argue that guaranteeing a booking this way is impractical for our purposes anyway (unless users use Origin exclusively) due to potential conflicts with other services. For example if someone lists their home on both Origin and Airbnb, and separate buyers book on both Origin and Airbnb at the same time. The 'guaranteed' booking would have to be cancelled on either Origin or Airbnb (and I'm pretty sure there are penalties for cancelling on Airbnb).

The way I see this working (using home sharing as an example) is as follows:

  1. Seller posts a listing in the same way as today. All pricing and availability variables are stored as JSON in IPFS. The listing contract stores only a list of booked slots.
  2. origin-js takes this JSON as input and provides convenience methods for showing pricing and calendar information for dapps. Booked slots (taken from the contract state) are also marked as unavailable.
  3. origin-js provides a 'buy' method which validates the listing is available for the given dates and calculates the correct price. The booking request is sent to the blockchain with a status of 'pending approval'. A corresponding booking JSON object is stored in IPFS (encrypted with seller's public key?) with stuff like number of guests, special requests, etc.
  4. Seller is notified of booking request. origin-js provides convenience methods for dapp to validate booking request given the pricing/availability rules specified in the listing JSON. This would prevent buyers submitting invalid booking JSON directly to IPFS and bypassing origin-js. Seller reviews request and approves.
  5. Disputes would be resolved the same way as with any other listing, via arbitration

Essentially the on-chain responsibilities would be limited to user validation (ERC725), escrow, arbitration and cancelations (eg return half the fee if cancelation is within 7 days). Even just that functionality I think would take a fair amount of gas.

Advantages

Disadvantage No guaranteed instant booking

wanderingstan commented 6 years ago

Non-instantbook

@nick is slowly winning me over to the non-instantbook camp.

And to highlight something that wasn't immediately obvious to me: In this scenario, the price-per-slot information is really just "suggested price" price information--the seller is free to accept or decline any offer.

Looking through the other Fractional Listing use-cases, the one that would most need instant-book IMO would be car-sharing (like Getaround). It would also need location, however, which we haven't begun to address.

Instantbook thoughts

OTOH, when we do instantbook (as we eventually must), I think the logic should be a separate module from the actual booking/purchase. That is, the resulting Purchase contract should look the same whether it came from a manually approved booking or an instantbook. The instantbook logic is simply an on-chain version of the off-chain service I described above, encoding the conditions under which the seller will accept a booking automatically. This neat segmentation would serve us well going forward.

nick commented 6 years ago

Agree we can always come back to instant booking later. Airbnb went live in 2009 but it wasn't until 2014 (when they were already at 7m bookings per year) that they introduced instant bookings. Using the simpler case to start with will allow us to implement this piece relatively quickly and move on to other major items such as messaging, dispute resolution, insurance, etc...

cuongdo commented 6 years ago

I also find myself being swayed by @nick's comments. For the sake of technical risk mitigation, I favor starting as simply as possible. Creating even a basic implementation of recurring availability slots on-chain is quite complex.

I'm curious to hear thoughts from @matthewliu and @joshfraser as well.

tyleryasaka commented 6 years ago

To add my 2 cents, I also tend agree with @nick 's approach. I think that whatever we do, we're going to get it wrong the first time. So rather than spending a tremendous amount of time and energy trying to build the ideal solution for these more complex use cases, it would be better to build a very minimal solution, put it out in the wild, and collect feedback on that and improve from there. In this specific scenario, I think that means not trying to support instant book right now, for the reasons @nick and @wanderingstan have listed. I think the aspect of launching a new platform and making it trustworthy could make it difficult for us to get instant book working in a usable way for a long time. (Seems like it will be a lot easier to get people to initially adopt Origin if they can use it alongside AirBnB and other services, as @wanderingstan and @nick have noted.)

That said - I do think we can possibly build out an experimental instant book option into our contract. For example, I like the idea of allowing sellers, upon creating a listing, to be able to point to an "instant book" contract for the listing, which would have a function that would return a boolean for whether or not a purchase is allowed, for a given slot id and ERC725 identity; and maybe a pricing function that would return a price for a given slot id. We could build out one or two specific instant book contracts for specific use cases, without trying to build out a generalized solution that will work for everything. (We might eventually get to a generalized solution, but I think we will need to experiment with more specific use cases before we have the knowledge we need to properly generalize.)

This way we could at least try out the idea of instant book for very simple use cases without being dependent on it. Of course that would require some development time so there would be a tradeoff. But I guess what I'm saying is, it doesn't have to be all or nothing. We can build out non-instant booking as our primary use case, and then experiment with opt-in instant booking on the side.

tyleryasaka commented 6 years ago

Re-reading @wanderingstan 's last comment, I think we are thinking along the same lines as far as separating the concern of purchase approval from everything else. We could have a purchase stage for awaiting approval, and perhaps a method on the purchase contract called isAutoApproved. If the listing contract has its instantBookContract property set with a contract address, then isAutoApproved will call that contract's isApproved method and return its result. If the instantBookContract property is not set, then isAutoApproved will return false, and the purchase contract will remain in the awaiting approval stage until the seller calls the purchase contract's approve method. Either way, once the purchase contract leaves the awaiting approval stage to the next stage, everything else would function exactly the same.

If we structure our contracts this way, then I think we will have full flexibility going forward. We can do any of the following:

As long as we keep the logic cleanly separated, we should be able to pick one strategy now but not be locked into it if we ever need to pivot in the future (which seems plausible if not likely).

matthewliu commented 6 years ago

From a purely product perspective, I don't see Instant Book as a must have requirement for v1. It would be great to have of course.

Instant book makes the most sense for ecommerce transactions (multiple quantity), some instances of hotels/homesharing, and instances of events (concerts, yoga class slots).

In the real world:

I would say the majority of our starting use cases can do without instant book, even if they are not optimal. As long as we don't peg ourselves into a corner that prevents this in the future, I'm fine pushing this out.

As for engineering implementation, most of you are much better informed than I am on the tradeoffs.

tyleryasaka commented 6 years ago

One last comment! During my last job where we were building a marketplace for recycled auto parts, we initially built the system where everything was automatically sold based off of the availability we had in our system. However, this had numerous problems, and we had a very high cancellation rate of our orders, which was very frustrating for buyers. The problem was that the sellers were using our system in addition to their other systems (because we were a fledgling marketplace and they were opting into our service as just an additional source of revenue, not the primary source). Their inventory would be sold from one of their other systems or over the counter, or they would find that the listing was incorrect and the part was actually not in working condition. This was in spite of a very complex system we had in place to sync with their existing inventory management systems.

For a major partner that we integrated into our system, one of their requirements was that we build an approval process for them, so that they would have a chance to manually review an order before it went through. So despite all of our efforts to build a fully automated system, we ended up building a manual approval process anyway.

In addition to the availability problems I just described, we had trust problems as well. Our sellers were manually approved into the system on a case-by-case basis for us, so that's how we ensured that the seller was trustworthy. But we also had buyer trust issues, and spent a lot of time dealing with fraud and returns. If we had this much trouble building a trustworthy marketplace in a centralized system, I can only imagine it will be more difficult in a decentralized system. All that to say, it seems like it will be a lot easier to build a trustworthy system around manual rather than automatic approvals, at least as a first iteration.

I don't know how much this experience applies to the use cases we're considering (I assume used auto parts is pretty low on our priority list). But it's just a personal experience I have with how difficult it is to build an automated approval system that just works.

franckc commented 6 years ago

Just want to share my experience on a past project I worked on that is in some sense related to this discussion.

At PayPal, my team implemented the incentive engine responsible for calculating how much Points, Gift Certificates and Coupons could be used by a user at checkout time to pay for eBay items. The rules were stored as XML blobs in a database table. The rule engine would take as input those rules and would output the list of incentives that could be used and associated amounts (ex: on this item, user can use 100 points and $20 GC and gets a 20% off by using coupon XX). The transactions recorded in the "ledger" database table would just record a pointer to the rule config that was used (e.g. a rule ID) and the amount of each incentive used. Having a configurable and editable set of rules that were decoupled from the transaction ledger gave us a lot of flexibility.

So overall, +1 for starting simple by storing the availability and pricing rules off-chain and using the chain mostly as a transaction ledger.

Just my 2 cents :)

cuongdo commented 6 years ago

OK, I believe we have a consensus that starting with approval-required listings and using off-chain storage (IPFS) for pricing & availability is the technically more feasible approach for 1.0. This allows us to economically use a subset of iCal/jCal to serialize the recurrence rules, which means we can save ourselves engineering time by using existing, tested JS libraries to parse the rules.

I'd also like to explore @tyleryasaka's idea to delegate approvals to external contracts. I'm curious what the potential security/abuse issues would be for that, and I'm sure some care will need to be exercised.

Unless I hear objections, I'll work on the design doc for approval-required fractional usage listings.

tyleryasaka commented 6 years ago

@cuongdo Yeah, I'll be glad to provide more details on the approach I was thinking. Probably will be easiest to do that once you have a first version of your design doc.

cuongdo commented 6 years ago

While there are still some incomplete parts, the design doc is ready for review: https://docs.google.com/document/d/131XzwVNGlZ6itINS_Gy0o0dYzRtN_5NQXbcqXZuL2vk/edit?usp=sharing

@joshfraser @wanderingstan @DanielVF: given the amount of thinking you've already done, I'm looking to you all for the bulk of the reviewing

@tyleryasaka: Please take a look, as you're prototyping the Origin.js part of this

nick commented 6 years ago

I think the confirmation step should be optional for regular listings too and not just fractional listings. For example I may want to offer something for sale only to a particular group of people (eg a discounted item to members of some organization), so an optional confirmation step could also be useful for regular listings. I may also want to offer something at a particular price 'or best offer'.

I think we should also try to be as generic as possible with terminology, for example 'request' instead of 'reserve' and just 'CONFIRMED' instead of 'RESERVATION_CONFIRMED'.

Regarding timezones, datetimes and reservation formats... how about we represent a slot as a single uint256:

4 bytes for slot format, 8 bytes for timezone, 10 bytes for start time, 10 bytes for end time.

If we represent in separate start, end and timezone variables that would take 3 times more space and offer no flexibility if we wish to offer alternative formats later on. Slot format '1' might mean '8 bytes for timezone, 10 bytes for start time, 10 bytes for end time' whereas slot format '2' could mean '14 bytes for start time, 14 bytes for end time'.

Do we need a 'seller rejected' phase? Since rejecting a listing costs gas, what incentive does the seller have to reject a request? Perhaps we should instead just rely on a timeout value (specified when the listing is created)? The seller can communicate that the listing is rejected (eg price should be higher, or slot is unavailable) off-chain via a channel specified in the reservation request IPFS JSON (eg IPFS pubsub, Telegram chat, etc). That channel would also be used for general communication between buyer/seller.

Regarding storing timezone and dates in IPFS: we could just use the JavaScript date object, which includes timezone information? E.g:

new Date().toString()
"Fri Jun 15 2018 10:25:02 GMT+0100 (British Summer Time)"

This will make it easier to parse in JS without a library, e.g.:

Number(new Date("Fri Jun 15 2018 10:25:02 GMT+0100 (British Summer Time)"))
1529054702000

We could also consider the Schema.org DateTime type: https://schema.org/DateTime

Regarding payments... I think the value in ETH (or other ERC20 token) should be sent along with the reservation request. If the seller does not accept within the specified time period, the buyer is automatically refunded. If the seller accepts the listing, the value goes into escrow until the booking is considered a success (eg x days after the booking ends, unless a dispute is initiated).

Overall looking good though, this is fun stuff to think about 😄

wanderingstan commented 6 years ago

Added a lot of little comments in-line in the doc. My big takeaways:

Meta issues

Adding our second Listing type brings a lot of issues to forefront that we could hide with only one listing type. E.g. adding inheritance to our contracts.

Also brings to a head the issue of versioning contracts--if the IPFS blob has a way of getting updated, we need some versioning. (Or figure out some other way to get the IPFS blob from the time the Purchase was created, to use as evidence for arbitration.)

And not to get ahead of ourselves: but we don't know how pricing will work on filecoin. It might get expensive to do edits to IPFS blobs!

Pricing & IPFS

Double Booking

Availability

Related to double booking: I don't think we need to mess with updating any IPFS to represent availability. Since the purchase contracts contain the start and end datetimes of the purchases, and since this data can be freely read from the blockchain, that's all the DApp needs to get availability. (And storing this data in two places is de-normalized, i.e. potential for corruption/confusion if they ever get out of sync.)

If we want to get fancy, we could write a solidity function that iterates over all purchases and returns an iCal file. :)

cuongdo commented 6 years ago

Thanks for reviewing the doc, @wanderingstan! My comments:

  1. It seems that at a minimum, we want weekend/weekday pricing for home sharing-listings. What would availability look like in this iCal format you're talking about? Would it look like a series of alternating blocks of 5 weekdays and 2 weekends? Or would it be two RRULES, one for weekdays and one for weekends?
  2. While I agree that showing availability is a convenience, I think that it's a useful one. I don't recall the last time I went to a reservation site (Airbnb, my barber, restaurant reservation) with no indication of availability. I think that would undesirable for UX.
  3. One idea for IPFS blob versioning is to include the previous hash in each IPFS blob. So, you'd be able to go back in time from the current hash.
  4. Good observation about not needing to store availability twice. For UX, it'd be good to allow the seller to block off days where the property is not available. It'd be strange if they had to make a Purchase to make that happen, so there should be another mechanism for doing that.
nick commented 6 years ago

One thing that worries me about iCal is the lack of good JS library support. It seems the only real option is ical.js which, looking at the commit history, doesn't get a lot of development. The API doesn't look all that friendly either, and from looking at it briefly I'm not sure supports all our use cases. I think we should at least investigate the library further, as well as other implementations (eg Python for the bridge server) to make sure we're not painting ourselves into a corner by making it a part of our protocol. I agree it can be useful for import/export for calendar apps, but I'm not sure of the benefits beyond that.

As an alternative, we can use a combination of objects from Schema.org (not to be confused with JSON Schema, which is for object validation). For our purposes, the following schemas could be useful:

The full list of Schema.org types is here

Here is a good example of using Schema.org objects to model a Hotel

Regarding availability, I think we should also think about having an option to store this information off-chain. The IPFS object could have an optional URL property where availability can be stored. Since availability is merely a 'suggestion' as Stan points out, having it 'verifiably correct' on IPFS and on-chain is less important. Having it optionally stored off-chain will allow users to update their calendar or subscribe to an Airbnb sync service (for example) without having to perform an on-chain update.

If you're a 'superhost' managing hundreds of properties and need to submit a blockchain transaction every time you need to update a reservation... it could become prohibitive.

wanderingstan commented 6 years ago

@cuongdo , I know you're out this week. Hope you don't check github too often. :)

  1. It seems that at a minimum, we want weekend/weekday pricing for home sharing-listings. What would availability look like in this iCal format you're talking about? Would it look like a series of alternating blocks of 5 weekdays and 2 weekends? Or would it be two RRULES, one for weekdays and one for weekends?

The pricing could be done as two RRULES, or by just listing out different prices for weekends for the foreseeable future. As @DanielVF has pointed out, we probably want relatively short-lived contracts, so having even a few hundred entries (for about a year's worth of weekends) isn't bad.

But the key point is that pricing is "just suggestions" to be displayed in the UI—we don't have to decide the absolute right way: One DApp could use RRules while another one just creates a ton of event entries. The end goal is that other DApps can consume this pricing data and display it on a calendar.

  1. While I agree that showing availability is a convenience, I think that it's a useful one. I don't recall the last time I went to a reservation site (Airbnb, my barber, restaurant reservation) with no indication of availability. I think that would undesirable for UX.

Sorry, I didn't communicate well: I meant availability is a "convenience" in the sense that it is not required for the final on-chain money-exchanges-hands Purchase contract. (I.e. It's not contractually binding and can't play any role in arbitration.) When we show an availability calendar to the user, we are implicitly saying "These are dates that the seller has indicated are available, but we have no way of confirming it, and this is in no way binding." We definitely want our DApp to show this to users, but ultimately the decision of book/no-book is up to the seller.

  1. One idea for IPFS blob versioning is to include the previous hash in each IPFS blob. So, you'd be able to go back in time from the current hash.

We will still need some sort of on-chain versioning: Otherwise, a seller could update the contract to point to a maliciously updated IPFS blob, which could include the hash of still another maliciously updated IPFS blob. E.g. I'm being taken to arbitration after the buyer points out that my house actually doesn't have a hot tub. I update my IPFS blob to remove the hot tub part. And if need be, I can create a fake "previous" IPFS blob for it to point to.

  1. Good observation about not needing to store availability twice. For UX, it'd be good to allow the seller to block off days where the property is not available. It'd be strange if they had to make a Purchase to make that happen, so there should be another mechanism for doing that.

I'm realizing that we actually have 2 sorts of availability:

wanderingstan commented 6 years ago

@nick : You've got me torn! iCal totally sucks, but its the de-facto standard for availability-calendar syncing (AirBnB, VRBO, and the rest.) Seems we'd need to support it at least as far as (1) having the DApp able to read an AirBnB/VRBO-generated iCal of existing bookings to mark as unavailable and (2) being able to generate a similar iCal that can be read by AirBnB/VRBO so they can mark off Origin-booked dates. Maybe this could just be derived from the schema.org data? (The good news is none of this needs to happen on-chain at least!)

BTW LodgingReservation seems like exactly something that could/should be stored in the Purchase contract for homesharing.

Pricing vs Availability : My thought was: Given that we need to support iCal for availability, it makes sense to also use it for pricing. Could be in a combined iCal file, or one for pricing and one for availability. But IMO from a purely data point of view, "July 4-5, 2018 is unavailable" is not far from of "July 4-5, 2018 is $121/night". So I'd suggest we use the same parsing/logic to process both.

...But maybe the schema.org stuff is certainly more comprehensive. @nick, can you elaborate on how you imagine them being used? I'm roughly thinking, the Listing would have IPFS which contains JSON schema with info about the home (or whatever resource), this json contains IPFS hash of another file which contains the availability/pricing information. But this is all getting pretty convoluted! I might start up some more diagrams. :)

nick commented 6 years ago

I think we can support iCal for import/export/sync without necessarily using it as the 'source of truth' in IPFS. Instead, we can define our own format for storing availability + pricing, then have toiCal / from iCal methods that marshal the data to and from the iCal format when we need to import/export/sync. This will give us more flexibility and allow us to write our own API without being constrained by the iCal format at the data storage level.

Does anyone here have an active Airbnb listing with upcoming booking so we can look at the iCal export to see what it supports? If it doesn't show pricing information, that will have to be inserted by the user manually when importing from Airbnb. Here are the instructions for exporting iCal from Airbnb.

wanderingstan commented 6 years ago

@nick This is a recent AirBnB iCal

Looks like whoever made the "standard" followed iCal's lead with having ALLCAPPS KEYS followed by values. :)

Sample data for one iCal event:

CHECKIN: 06/14/2017
CHECKOUT: 06/16/2017
NIGHTS: 2
PHONE:  +1 (XXX) XXX-XXXX
EMAIL: (no email alias available)
PROPERTY: 2BD near Moscone, Union Square & BART
wanderingstan commented 6 years ago

we can define our own format for storing availability + pricing, then have toiCal / from iCal methods

I like this approach, but we'd need some iCal file at a URL that persists (for AirBnB servers to read), which I'm assuming would be an IPFS blob on our gateway. (Like the iCal link in previous comment). And we'd like this iCal to be discoverable (by other Dapps, indexers, etc) so there would need to be some reference to it from our Listing IPFS or contract.

nick commented 6 years ago

Sure, exporting to a separate IPFS blob would work well I think... then we can have a pointer to that in the main listing. We can also consider leveraging IPNS so we can update availability and pricing without having to update the IPFS hash on-chain.

tyleryasaka commented 6 years ago

This is a long thread!

I've been tasked with putting together a working prototype for fractional usage contracts. I've read through @cuongdo 's design doc as well as everyone's comments and considered the different ideas proposed.

My goal is to follow agile principles and keep this first iteration as simple as possible. I think the discussion here is great in the sense that we're thinking things through and making sure we've considered many angles before we start building. That said, I don't think we need to (or can) solve all of these detailed problems in the first iteration. I think we need to get something simple but functional out there and experiment with it.

With that in mind, this is what I'm thinking for the first prototype:

  1. I'm going to keep both pricing and availability off-chain for now. I'll structure the contracts so that we will be able to easily support instant booking contracts in the future, but won't worry much about that now. My reasoning for doing availability off-chain right now is that it's simpler, and not necessary for a functional prototype. I am not necessarily opposed to storing availability data on-chain (haven't really thought this through enough to have an opinion), but I do think that the cost/benefit ratio for doing that in an initial prototype is not sufficient to justify it. (I'll take great care to structure the contracts so that we can easily add on-chain availability whenever we decide to do that.)
  2. I will support versioning for the IFPS hash because that seems like an essential feature (and is trivial to implement), as @wanderingstan has discussed.
  3. iCal debate: no opinion yet. I will get a first prototype of the contracts in place first as iCal stuff will be completely off-chain right now - then I will revisit the iCal debate.
  4. Purchase contract structure (see below)

Purchase contract proposal

After experimenting with the solidity contracts, I'm convinced that our best course of action (for now at least) is to have a single purchase contract for both types of listings. What this means is that we'll delegate some logic back to the listing contract (such as checking that payment is sufficient). It also means that we will want to simplify the names of our stages so that they're more generic. We basically want all of our listing purchases to go through the same states, even if the state changes get triggered in different ways for different types of listings, or if certain stages are skipped altogether for certain types.

I'm proposing the following stages, for this first iteration:

For our current listings, the state machine looks like:

For our fractional usage listings:

I've already made a good amount of progress on a branch for this prototype: https://github.com/originprotocol/origin-js/tree/tyleryasaka/fractional-usage

wanderingstan commented 6 years ago

This thread is long but rich! I'm learning a ton and agree that these are fun, interesting problems!

@tyleryasaka Can't wait to try your branch. What would be a good entry-point for trying it out? Looking at the smart contracts?

schema.org+ jsonschema

...getting a little more general than just Fractional:

It crystalized for me on the flight that what we probably want is to replace the schemas @joshfraser admitted to making in a half hour last year (😀) with the schema.org schemas @nick found (Like Apartment ), but we'd need to encode them in JSON schema. Presumably in JSON-LD, schema.org's preferred json format.

A little rummaging on the web led me to what seems to be the definitive thread on the subject of JSON-LD + JSON-schema. But I'll freely admit that I don't fully grok the discussion—can't even tell if an answer was reached! Is this what we'd want to do?

(I'm 90% sure that we want to use JSON schema. But is there any argument for me made for say, using "plain" JSON-LD and writing our own form-renderer? I predict that we will outgrow react-jsonschema-form pretty soon, as Aure designs kick-ass UI/UX flows that can't be captured by a generic renderer.)

Random: Learned that ISO 8601 (my favorite datetime format) also has a way to define repeating intervals, but it's very limited. E.g. no way to define something being on weekends.