Skip to content

Commit 3ffbcc7

Browse files
committed
les: add lespay API
1 parent 872b4ae commit 3ffbcc7

File tree

3 files changed

+273
-7
lines changed

3 files changed

+273
-7
lines changed

internal/web3ext/web3ext.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var Modules = map[string]string{
3333
"swarmfs": SwarmfsJs,
3434
"txpool": TxpoolJs,
3535
"les": LESJs,
36+
"lespay": LESPAYJs,
3637
}
3738

3839
const ChequebookJs = `
@@ -856,3 +857,50 @@ web3._extend({
856857
]
857858
});
858859
`
860+
861+
const LESPAYJs = `
862+
web3._extend({
863+
property: 'lespay',
864+
methods:
865+
[
866+
new web3._extend.Method({
867+
name: 'connection',
868+
call: 'lespay_connection',
869+
params: 6
870+
}),
871+
new web3._extend.Method({
872+
name: 'deposit',
873+
call: 'lespay_deposit',
874+
params: 4
875+
}),
876+
new web3._extend.Method({
877+
name: 'buyTokens',
878+
call: 'lespay_buyTokens',
879+
params: 6
880+
}),
881+
new web3._extend.Method({
882+
name: 'buyTokens',
883+
call: 'lespay_sellTokens',
884+
params: 6
885+
}),
886+
new web3._extend.Method({
887+
name: 'getBalance',
888+
call: 'lespay_getBalance',
889+
params: 2
890+
}),
891+
new web3._extend.Method({
892+
name: 'info',
893+
call: 'lespay_info',
894+
params: 2
895+
}),
896+
new web3._extend.Method({
897+
name: 'receiverInfo',
898+
call: 'lespay_receiverInfo',
899+
params: 3
900+
}),
901+
],
902+
properties:
903+
[
904+
]
905+
});
906+
`

les/api.go

Lines changed: 217 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717
package les
1818

1919
import (
20+
"context"
2021
"errors"
2122
"fmt"
22-
"math"
2323
"time"
2424

2525
"github.com/ethereum/go-ethereum/common/hexutil"
2626
"github.com/ethereum/go-ethereum/common/mclock"
27+
"github.com/ethereum/go-ethereum/p2p/discv5"
2728
"github.com/ethereum/go-ethereum/p2p/enode"
29+
"github.com/ethereum/go-ethereum/rlp"
2830
)
2931

3032
var (
@@ -35,8 +37,6 @@ var (
3537
errNoPriority = errors.New("priority too low to raise capacity")
3638
)
3739

38-
const maxBalance = math.MaxInt64
39-
4040
// PrivateLightServerAPI provides an API to access the LES light server.
4141
type PrivateLightServerAPI struct {
4242
server *LesServer
@@ -105,12 +105,12 @@ func (api *PrivateLightServerAPI) clientInfo(c *clientInfo, id enode.ID) map[str
105105
pb, nb := c.balanceTracker.getBalance(now)
106106
info["pricing/balance"], info["pricing/negBalance"] = pb, nb
107107
info["pricing/balanceMeta"] = c.balanceMetaInfo
108-
info["priority"] = pb != 0
108+
info["priority"] = pb.base != 0
109109
} else {
110110
info["isConnected"] = false
111111
pb := api.server.clientPool.ndb.getOrNewPB(id)
112112
info["pricing/balance"], info["pricing/balanceMeta"] = pb.value, pb.meta
113-
info["priority"] = pb.value != 0
113+
info["priority"] = pb.value.base != 0
114114
}
115115
return info
116116
}
@@ -150,7 +150,7 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien
150150
setFactor(&negFactors.requestFactor)
151151
case !defParams && name == "capacity":
152152
if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity {
153-
err = api.server.clientPool.setCapacity(client, uint64(capacity))
153+
_, _, err = api.server.clientPool.setCapacity(client.id, client.freeID, uint64(capacity), 0, true)
154154
// Don't have to call factor update explicitly. It's already done
155155
// in setCapacity function.
156156
} else {
@@ -184,7 +184,7 @@ func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[str
184184
if client != nil {
185185
update, err := api.setParams(params, client, nil, nil)
186186
if update {
187-
client.updatePriceFactors()
187+
updatePriceFactors(&client.balanceTracker, client.posFactors, client.negFactors, client.capacity)
188188
}
189189
return err
190190
} else {
@@ -352,3 +352,213 @@ func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
352352
}
353353
return api.backend.oracle.config.Address.Hex(), nil
354354
}
355+
356+
// PrivateLespayAPI provides an API to use the LESpay commands of either the local or a remote server
357+
type PrivateLespayAPI struct {
358+
peerSet *peerSet
359+
clientHandler *clientHandler
360+
dht *discv5.Network
361+
tokenSale *tokenSale
362+
}
363+
364+
// NewPrivateLespayAPI creates a new LESPAY API.
365+
func NewPrivateLespayAPI(peerSet *peerSet, clientHandler *clientHandler, dht *discv5.Network, tokenSale *tokenSale) *PrivateLespayAPI {
366+
return &PrivateLespayAPI{
367+
peerSet: peerSet,
368+
clientHandler: clientHandler,
369+
dht: dht,
370+
tokenSale: tokenSale,
371+
}
372+
}
373+
374+
// makeCall sends an encoded command to either the local or a remote server and returns the encoded reply
375+
//
376+
// Note: nodeStr can represent either the node ID of a connected node or the full enode of any remote node.
377+
// If remote is true then the command is sent to the specified node. It is sent through LES if it was specified
378+
// with node ID, throush UDP talk otherwise.
379+
// If remote is false then the command is executed locally, with the specified remote node assumed as sender.
380+
func (api *PrivateLespayAPI) makeCall(ctx context.Context, remote bool, nodeStr string, cmd []byte) ([]byte, error) {
381+
var (
382+
id enode.ID
383+
freeID string
384+
peer *peer
385+
node *enode.Node
386+
err error
387+
)
388+
if nodeStr != "" {
389+
if id, err = enode.ParseID(nodeStr); err == nil {
390+
if peer = api.peerSet.Peer(peerIdToString(id)); peer == nil {
391+
return nil, errors.New("peer not connected")
392+
}
393+
freeID = peer.freeClientId()
394+
} else {
395+
var err error
396+
if node, err = enode.Parse(enode.ValidSchemes, nodeStr); err == nil {
397+
id = node.ID()
398+
freeID = node.IP().String()
399+
} else {
400+
return nil, err
401+
}
402+
}
403+
}
404+
405+
if remote {
406+
var (
407+
reply []byte
408+
cancelFn func() bool
409+
)
410+
delivered := make(chan struct{})
411+
if peer != nil {
412+
// remote call to a connected peer through LES
413+
if api.clientHandler == nil {
414+
return nil, errors.New("client handler not available")
415+
}
416+
cancelFn = api.clientHandler.makeLespayCall(peer, cmd, func(r []byte, delay uint) bool {
417+
reply = r
418+
close(delivered)
419+
return reply != nil
420+
})
421+
} else {
422+
// remote call through UDP TALK
423+
if api.dht == nil {
424+
return nil, errors.New("UDP DHT not available")
425+
}
426+
cancelFn = api.dht.SendTalkRequest(node, "lespay", [][]byte{cmd}, func(payload interface{}, delay uint) bool {
427+
if replies, ok := payload.([]interface{}); ok && len(replies) == 1 {
428+
reply, _ = replies[0].([]byte)
429+
}
430+
close(delivered)
431+
return reply != nil
432+
})
433+
}
434+
select {
435+
case <-time.After(time.Second * 5):
436+
cancelFn()
437+
return nil, errors.New("timeout")
438+
case <-ctx.Done():
439+
cancelFn()
440+
return nil, ctx.Err()
441+
case <-delivered:
442+
if len(reply) == 0 {
443+
return nil, errors.New("unknown command")
444+
}
445+
return reply, nil
446+
}
447+
} else {
448+
if api.tokenSale == nil {
449+
return nil, errors.New("token sale module not available")
450+
}
451+
// execute call locally
452+
return api.tokenSale.runCommand(cmd, id, freeID), nil
453+
}
454+
455+
}
456+
457+
// Connection checks whether it is possible with the current balance levels to establish
458+
// requested connection or capacity change and then stay connected for the given amount
459+
// of time. If it is possible and setCap is also true then the client is activated of the
460+
// capacity change is performed. If not then returns how many tokens are missing and how
461+
// much that would currently cost using the specified payment module(s).
462+
func (api *PrivateLespayAPI) Connection(ctx context.Context, remote bool, node string, requestedCapacity, stayConnected uint64, paymentModule []string, setCap bool) (results tsConnectionResults, err error) {
463+
params := tsConnectionParams{requestedCapacity, stayConnected, paymentModule, setCap}
464+
enc, _ := rlp.EncodeToBytes(&params)
465+
var resEnc []byte
466+
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsConnection}, enc...))
467+
if err != nil {
468+
return
469+
}
470+
err = rlp.DecodeBytes(resEnc, &results)
471+
return
472+
}
473+
474+
// Deposit credits a payment on the sender's account using the specified payment module
475+
func (api *PrivateLespayAPI) Deposit(ctx context.Context, remote bool, node, paymentModule, proofOfPayment string) (results tsDepositResults, err error) {
476+
var proof []byte
477+
if proof, err = hexutil.Decode(proofOfPayment); err != nil {
478+
return
479+
}
480+
params := tsDepositParams{paymentModule, proof}
481+
enc, _ := rlp.EncodeToBytes(&params)
482+
var resEnc []byte
483+
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsDeposit}, enc...))
484+
if err != nil {
485+
return
486+
}
487+
err = rlp.DecodeBytes(resEnc, &results)
488+
return
489+
}
490+
491+
// BuyTokens tries to convert the permanent balance (nominated in the server's preferred
492+
// currency, PC) to service tokens. If spendAll is true then it sells the maxSpend amount
493+
// of PC coins if the received service token amount is at least minReceive. If spendAll is
494+
// false then is buys minReceive amount of tokens if it does not cost more than maxSpend
495+
// amount of PC coins.
496+
// if relative is true then maxSpend and minReceive are specified relative to their current
497+
// balances. In this case maxSpend represents the amount under which the PC balance should
498+
// not go and minReceive represents the amount the service token balance should reach.
499+
// This mode is useful when actual conversion is intended to happen and the sender has to
500+
// retry the command after not receiving a reply previously. In this case the sender cannot
501+
// be sure whether the conversion has already happened or not. If relative is true then it
502+
// is impossible to do a conversion twice. In exchange the sender needs to know its current
503+
// balances (which it probably does if it has made a previous call to just ask the current price).
504+
func (api *PrivateLespayAPI) BuyTokens(ctx context.Context, remote bool, node string, maxSpend, minReceive uint64, relative, spendAll bool) (results tsBuyTokensResults, err error) {
505+
params := tsBuyTokensParams{maxSpend, minReceive, relative, spendAll}
506+
enc, _ := rlp.EncodeToBytes(&params)
507+
var resEnc []byte
508+
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsBuyTokens}, enc...))
509+
if err != nil {
510+
return
511+
}
512+
err = rlp.DecodeBytes(resEnc, &results)
513+
return
514+
}
515+
516+
// SellTokens tries to convert service tokens to permanent balance (nominated in the server's
517+
// preferred currency, PC). Parameters work similarly to BuyTokens.
518+
func (api *PrivateLespayAPI) SellTokens(ctx context.Context, remote bool, node string, maxSell, minRefund uint64, relative, sellAll bool) (results tsSellTokensResults, err error) {
519+
params := tsSellTokensParams{maxSell, minRefund, relative, sellAll}
520+
enc, _ := rlp.EncodeToBytes(&params)
521+
var resEnc []byte
522+
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsSellTokens}, enc...))
523+
if err != nil {
524+
return
525+
}
526+
err = rlp.DecodeBytes(resEnc, &results)
527+
return
528+
}
529+
530+
// GetBalance returns the current PC balance and service token balance
531+
func (api *PrivateLespayAPI) GetBalance(ctx context.Context, remote bool, node string) (results tsGetBalanceResults, err error) {
532+
var resEnc []byte
533+
resEnc, err = api.makeCall(ctx, remote, node, []byte{tsGetBalance})
534+
if err != nil {
535+
return
536+
}
537+
err = rlp.DecodeBytes(resEnc, &results)
538+
return
539+
}
540+
541+
// Info returns general information about the server, including version info of the
542+
// lespay command set, supported payment modules and token expiration time constant
543+
func (api *PrivateLespayAPI) Info(ctx context.Context, remote bool, node string) (results tsInfoApiResults, err error) {
544+
var resEnc []byte
545+
resEnc, err = api.makeCall(ctx, remote, node, []byte{tsInfo})
546+
if err != nil {
547+
return
548+
}
549+
err = rlp.DecodeBytes(resEnc, &results)
550+
return
551+
}
552+
553+
// ReceiverInfo returns information about the specified payment receiver(s) if supported
554+
func (api *PrivateLespayAPI) ReceiverInfo(ctx context.Context, remote bool, node string, receiverIDs []string) (results tsReceiverInfoApiResults, err error) {
555+
params := tsReceiverInfoParams(receiverIDs)
556+
enc, _ := rlp.EncodeToBytes(&params)
557+
var resEnc []byte
558+
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsReceiverInfo}, enc...))
559+
if err != nil {
560+
return
561+
}
562+
err = rlp.DecodeBytes(resEnc, &results)
563+
return
564+
}

les/client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
type LightEthereum struct {
4949
lesCommons
5050

51+
srvr *p2p.Server
5152
reqDist *requestDistributor
5253
retriever *retrieveManager
5354
odr *LesOdr
@@ -206,6 +207,12 @@ func (s *LightEthereum) APIs() []rpc.API {
206207
Service: NewPrivateLightAPI(&s.lesCommons),
207208
Public: false,
208209
},
210+
{
211+
Namespace: "lespay",
212+
Version: "1.0",
213+
Service: NewPrivateLespayAPI(s.lesCommons.peers, s.handler, s.srvr.DiscV5, nil),
214+
Public: false,
215+
},
209216
}...)
210217
}
211218

@@ -235,6 +242,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol {
235242
// light ethereum protocol implementation.
236243
func (s *LightEthereum) Start(srvr *p2p.Server) error {
237244
log.Warn("Light client mode is an experimental feature")
245+
s.srvr = srvr
238246

239247
// Start bloom request workers.
240248
s.wg.Add(bloomServiceThreads)

0 commit comments

Comments
 (0)