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

allow third party services to charge the user #445

Closed
Tracked by #1317
xmonader opened this issue Sep 13, 2022 · 10 comments
Closed
Tracked by #1317

allow third party services to charge the user #445

xmonader opened this issue Sep 13, 2022 · 10 comments
Assignees

Comments

@xmonader
Copy link
Contributor

according to the erc20 interface

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

these functions support third party billing, where

  • third party services allowed to spend on behalf of the user to an upperlimit
  • the user sets the max allowance to be spent by a service
  • the third party services can check the allowed remaining amount they can spent so they can notify the users to topup their allowance
@xmonader xmonader added type_feature New feature or request type_story labels Sep 13, 2022
@LeeSmet
Copy link
Contributor

LeeSmet commented Sep 13, 2022

I'm not a fan of this. Erc20 is specifically meant for general purpose assets on top of ethereum, while TFT is already a native currency on tfchain. Furthermore, in this context, we use a contract to govern the payment between 2 parties. To this extend, I prefer the design and implementation of a GenericContract, which handles billing. This gives more control than the erc20 interface, and fits better in the current architecture.

Specifically, I'm thinking off something like this:

  • Service creates a GenericContract on chain between itself and a user, both identified by an address.
  • The contract contains a recurring cost, which is billed every hour simply for existing
  • The contract also contains a variable cost up to some amount per some time (probably per hour). This expresses a variable amount which can be set by the service every hour to bill (with an upper limit to protect a user from malicious services). By including this there can be custom fit billing for variable resource usage.
  • The user then agrees to the contract, only after which it can actually start billing
  • Optionally there can be some custom metadata included by either the service or the user, not sure if this is needed.

The above follows the trend of using contracts, which means after creation a user does not need to care anymore. Furthermore you can track easily per contract how much you pay over time, how much you can pay maximum, and you don't have to continuously increase your allowance for some 3rd party.

@sameh-farouk
Copy link
Member

sameh-farouk commented Sep 14, 2022

@xmonader @LeeSmet Here is another approach to be considered.
Zos deployments work in a similar fashion I believe.

We can have a contract with pin method which takes a content hash and TFT amounts.
pinning service would listen for that contract on-chain events, pins the hashes, and unpinned it when received another event that states TFT amount consumed.

This approach won't expose public pinning API. pinning and unpinning will mainly be done by listening to chain events.

We could also have a hybrid approach to make the API follow the specs of IPFS pinning service. like after sending the content hash to the pin contract we could return a unique token, which can be used by a pinning client to pin the same hash. the service will validate the token and the hash from the chain and then pin the content. the pinning period will depend on the content size and the amount of TFT deposited.

@DylanVerstraete DylanVerstraete added this to the 2.2.0 milestone Sep 21, 2022
@sasha-astiadi
Copy link
Member

@xmonader please put an assignee if already on 'accepted '

@xmonader
Copy link
Contributor Author

@LeeSmet please prepare the specs for this and have consensus with @DylanVerstraete to move forward

@LeeSmet
Copy link
Contributor

LeeSmet commented Oct 17, 2022

Since we don't want to do work on the chain for every possible 3rd party service, we will keep this as generic as possible. While custom implementations for services might offer small advantages in the flow, the extra effort to develop and most importantly maintain these implementations is not worth it compared to a proper generic flow which would technically also be reusable.

Contract structure

A contract will work simple client - server principle (i.e. the "buyer" and the "seller", a "consumer" of a service and one who "offers" the service). Both parties are identified by a twin id to fit in the current flow (we could use a generic address as well here). Contract is laid out as such

  • consumer twin id
  • service twin id
  • base fee, this is the fixed amount which will be billed hourly
  • variable fee, this is the maximum amount which can be billed on top of the base fee (for variable consumption metrics, to be defined by the service)
  • metadata, a field which just holds some bytes. The service can use this any way it likes (including having stuff set by the user). We limit this field to some size now, suggested 64 bytes ( 2 public keys generally)

Additionally, we also keep track of some metadata, which will be:

  • accepted by consumer
  • accepted by service
  • last bill received (we keep track of this to make sure the service does not send overlapping bills)

Billing

Once a contract is accepted by both the consumer and the service, the chain can start accepting "bill reports" from the service for the contract. Only the twin of the service can send these, as specified in the contract. The bill contains the following:

  • variable amount which is billed. The chain checks that this is less than or equal to the variable amount as specified in the contract, if it is higher the bill is rejected for overcharging. Technically the service could send a report every second to drain the user. To protect against this, the max variable amount in the contract is interpreted as being "per hour", and the value set in the contract is divided by 3600, multiplied by window size, to find the actual maximum which can be billed by the contract.
  • window, this is the amount of time (in seconds) covered since last contract. The chain verifies that current time - window >= contract.last_bill, such that no bills overlap to avoid overcharging. Combined with the previous limit to variable amount this prevents the service from overcharging the user.
  • Some optional metadata, this will again just be some bytes (the service decides how this can be interpreted). For now we'll limit this to 50 bytes or so

Chain calls

Callable by anyone

  • create_contract(consumer twin ID, service twin ID): Creates the contract and sets the id's. Base fee and variable fee are left at 0

Callable by consumer or service

  • set_metadata(data): Sets the custom metadata on the contract. This can be done by either the client of the service, depending on how it is interpreted (as specified by the service). For now, we will assume that setting metadata is a one off operation. As a result, if metadata is already present when this is called, an error is thrown (i.e. only the first call of this function can succeed).

Callable by service

  • set_fees(base, variable): Sets the base fee and variable fee on the contract
  • reject_by_service(): Rejects the contract, deleting it.
  • approve_by_service(): Sets the service_accepted flag on the contract. After this, no more modifications to fees or metadata can be done

Callable by user

  • reject_by_consumer(): Rejects the contract, deleting it.
  • approve_by_consumer(): Sets the consumer_accepted flag on the contract. After this, no more modifications to fees or metadata can be done

Flow

We start of by creating a contract. This can technically be done by anyone, but in practice will likely end up being done by either the service or the consumer (depending on what the service expects). This will be followed by the service or consumer setting the metadata (again depending on how the service expects things to be), and the service setting a base fee + variable fee. Note that part of the communication can and should be off chain, the contract is only the finalized agreement. When the fees and metadata are set, both the consumer and service need to explicitly approve the contract, setting the appropriate flag on the contract. Note that as soon as either party accepted (i.e. either flag is set), the fees and metadata cannot be changed anymore. It is technically possible for consumers to accept a contract as soon as it is created, thereby not giving the service a chance to set the fees. Though this basically means the contract is invalid and the service should just outright reject it.

Once the contract is accepted by both the consumer and the service, it can be billed (i.e. bills send before both flags are set must be rejected). Because a service should not charge the user if it doesn't work, we will require that bills be send every hour, by limiting the window size to 3600. Anything with a bigger window is rejected. This way if the service is down (for some longer period), it for sure can't bill for the time it was down. When the bill is received, the chain calculates contract.base_fee * bill.window / 3600 + variable fee (keeping in mind the constraint for variable fee as outlined above), and this amount is transferred from the consumer twin account to the service twin account.

We will not implement a grace period for this right now, as the service should define on an individual basis how this is handled. If needed in the future this can of course change.

@DylanVerstraete
Copy link
Contributor

DylanVerstraete commented Nov 16, 2022

Progress on tfchain: #495

Things left to do here:

@xmonader
Copy link
Contributor Author

Need a status update here, as this was rebased on the work of the power mgmt and the capacity planning? @renauter @DylanVerstraete ?

@renauter
Copy link
Collaborator

Need a status update here, as this was rebased on the work of the power mgmt and the capacity planning? @renauter @DylanVerstraete ?

Power mgmt and the capacity planning was reverted
3rd party service contract is now independent from it and has been already merged and tagged https://github.com/threefoldtech/tfchain/releases/tag/2.2.0-rc7
Substrate go client is also already synchronized
Tomorrow we handle graphql
And will be able to deploy on devnet

@zaelgohary
Copy link

Verified.

serviceContractCreate

image

serviceContractSetMetadata (by service)

image

serviceContractsSetFees (by service)

image

serviceContractApprove (by service)

image

serviceContractReject (by service)

image

serviceContractApprove (by user)

image

serviceContractReject (by user)

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Status: Done
Development

No branches or pull requests

7 participants