Skip to content

Instantly share code, notes, and snippets.

@zsfelfoldi
Created November 19, 2019 14:03
Show Gist options
  • Save zsfelfoldi/9068aabe1b90f8583b04077dc15836a3 to your computer and use it in GitHub Desktop.
Save zsfelfoldi/9068aabe1b90f8583b04077dc15836a3 to your computer and use it in GitHub Desktop.

Payment module

LES in-protocol payments can be sent using multiple payment technologies or even multiple currencies. These are realized as plug-in modules. The payment sender module generates a "proof of payment" that the receiver module can interpret and verify. These proofs can be transmitted through an LES connection or LESTALK UDP messages.

Note: the design allows using multiple currencies on both the client and server side. Each payment module uses one specific currency but it can handle value conversions on both ends. In the first implementation we won't allow this though, the first payment module should only handle ETH. This makes value calculations a lot easier for requestPayment and paymentCost. Expected transaction costs have to be considered but that can be very simple too (hardcoded gas cost, user specified gas price).

Payment receiver module

  • func receivePayment(from enode.ID, proofOfPayment, oldMeta []byte) (value uint64, newMeta []byte, err error)

Interprets proofOfPayment and returns value which is the estimated value of the current payment. The receiver is responsible for cashing cheques in time. It should also take estimated future transaction costs into account and ensure that the long-term sum of returned values stays close to the net amount of money cashed (minus transaction costs). If the currency preferred by the token sale mechanism is not the same as the received currency then value should be nominated in the former. In this case the receiver should either inform the server operator about the currency risk and the source of conversion rates or take care of conversions automatically and take responsibility for estimating the conversion rates when receiving payments (maybe even use a derivative market to hedge against price changes between receiving the proof of payment and actually converting the cashed coins).

The tags oldMeta and newMeta are generated and interpreted by the receiver. Payment processing is stateful and in order to avoid getting the state of token balances and the payment receiver out of sync, these tags can hold a reference to the latest payment added to the client account or even hold the entire internal state of the receiver related to that client. value should represent the amount received since the state referenced by oldMeta.

Note that this function can access the local state database (the receiver runs on a full node so it has the entire recent state available) but it should not block for a long time. proofOfPayment should contain enough information to tell the value of the transfer without any further network retrievals.

  • func requestPayment(from enode.ID, value uint64, meta []byte) uint64

Returns the necessary amount to be sent (nominated in the currency of payment) in order for the server to receive the given expected value (nominated in the preferred currency of the token sale). In case of a simple one-way payment channel and no currency conversion the return value is usually the same as the value parameter. For the first payment request (or the first one after cashing a cheque) the return value also includes the expected transaction cost of cashing the next one.

  • func info() []byte

Returns information about the parameters and capabilities/restrictions of the payment method that the payment sender module on the client side can interpret and decide whether the other side is suitable for receiving its transactions.

Payment sender module

  • func pay(to common.Address, amount, maxCost uint64, oldMeta []byte) (proofOfPayment, newMeta []byte, cost uint64)

Calculates the expected cost (nominated in the client's preferred currency) of sending the given amount to the specified address and returns it in the cost field. If cost <= maxCost then then payment is approved and proofOfPayment is generated (otherwise it is nil). The tags oldMeta and newMeta serve the same purpose as in receivePayment.

Token sale module

The token sale module is responsible for handling the sale, limitation and expiraton of service tokens. It has a preferred currency in which token prices are nominated but it does not handle any external currencies, it leaves that to the payment receiver module(s) and believes the received value amounts reported by them. Since reported values are always converted to the preferred currency of the token sale the sale module only maintains a "preferred currency balance" (pcBalance) in addition to the token balance and it is the only external currency type this module should know about. pcBalance and the internal state references (receiverMeta tags) of payment receiver modules are stored in the meta tag of the client balance entries stored by the server API.

  • func connection(id enode.ID, requestedCapacity uint64, stayConnected time.Duration, paymentModule []string, connect bool) -> (availableCapacity, tokenBalance, tokensMissing uint64, paymentRequired []uint64)

Lets clientPool calculate the amount of tokens necessary for the requested connection. If the current tokenBalance is less than the necessary amount then tokensMissing is positive (zero otherwise). In this case the requestPayment function of the specified payment module(s) is called in order to calculate paymentRequired. If tokensMissing is zero and connect is true then the connection is established or the capacity is updated if the connection already exists. Note that the connect parameter is useful only if the request is sent through an LES connection.

  • func deposit(from enode.ID, paymentModule string, proofOfPayment []byte) (value uint64, err error)

Deposits a payment to the client's balance. Note that the amount is not converted to service tokens automatically.

First the meta field of the client balance entry is retrieved. pcBalance and the relevant receiverMeta tag are extracted, then receivePayment of the specified payment module is called. The returned value is added to pcBalance and the old receiverMeta is replaced by the returned newMeta. Then the client balance meta entry is reconstructed and stored with an addBalance API call (where the value field is zero since we are not adding service tokens yet).

  • func buyTokens(from enode.ID, currencyID string, maxSpent, minExpected uint64) (spent, required, received, currencyBalance, tokenBalance uint64)

Performs the token sale if possible and calls addBalance to update both the token balance and pcBalance. If the conversion was successful then required is zero, otherwise spent and received are zero. If the available rate is better than expected by the client then the entire maxSpent amount is converted and received will be higher than minExpected.

  • func paymentInfo(paymentModule []string) map[string][]byte

Returns the info() data packages of the payment modules that are both requested and available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment