mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
les: add lespay API
This commit is contained in:
parent
46fa5c031b
commit
cf796e5773
3 changed files with 273 additions and 7 deletions
|
|
@ -33,6 +33,7 @@ var Modules = map[string]string{
|
||||||
"swarmfs": SwarmfsJs,
|
"swarmfs": SwarmfsJs,
|
||||||
"txpool": TxpoolJs,
|
"txpool": TxpoolJs,
|
||||||
"les": LESJs,
|
"les": LESJs,
|
||||||
|
"lespay": LESPAYJs,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChequebookJs = `
|
const ChequebookJs = `
|
||||||
|
|
@ -856,3 +857,50 @@ web3._extend({
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const LESPAYJs = `
|
||||||
|
web3._extend({
|
||||||
|
property: 'lespay',
|
||||||
|
methods:
|
||||||
|
[
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'connection',
|
||||||
|
call: 'lespay_connection',
|
||||||
|
params: 6
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'deposit',
|
||||||
|
call: 'lespay_deposit',
|
||||||
|
params: 4
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'buyTokens',
|
||||||
|
call: 'lespay_buyTokens',
|
||||||
|
params: 6
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'buyTokens',
|
||||||
|
call: 'lespay_sellTokens',
|
||||||
|
params: 6
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getBalance',
|
||||||
|
call: 'lespay_getBalance',
|
||||||
|
params: 2
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'info',
|
||||||
|
call: 'lespay_info',
|
||||||
|
params: 2
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'receiverInfo',
|
||||||
|
call: 'lespay_receiverInfo',
|
||||||
|
params: 3
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
properties:
|
||||||
|
[
|
||||||
|
]
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
|
||||||
224
les/api.go
224
les/api.go
|
|
@ -17,14 +17,16 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -35,8 +37,6 @@ var (
|
||||||
errNoPriority = errors.New("priority too low to raise capacity")
|
errNoPriority = errors.New("priority too low to raise capacity")
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxBalance = math.MaxInt64
|
|
||||||
|
|
||||||
// PrivateLightServerAPI provides an API to access the LES light server.
|
// PrivateLightServerAPI provides an API to access the LES light server.
|
||||||
type PrivateLightServerAPI struct {
|
type PrivateLightServerAPI struct {
|
||||||
server *LesServer
|
server *LesServer
|
||||||
|
|
@ -105,12 +105,12 @@ func (api *PrivateLightServerAPI) clientInfo(c *clientInfo, id enode.ID) map[str
|
||||||
pb, nb := c.balanceTracker.getBalance(now)
|
pb, nb := c.balanceTracker.getBalance(now)
|
||||||
info["pricing/balance"], info["pricing/negBalance"] = pb, nb
|
info["pricing/balance"], info["pricing/negBalance"] = pb, nb
|
||||||
info["pricing/balanceMeta"] = c.balanceMetaInfo
|
info["pricing/balanceMeta"] = c.balanceMetaInfo
|
||||||
info["priority"] = pb != 0
|
info["priority"] = pb.base != 0
|
||||||
} else {
|
} else {
|
||||||
info["isConnected"] = false
|
info["isConnected"] = false
|
||||||
pb := api.server.clientPool.ndb.getOrNewPB(id)
|
pb := api.server.clientPool.ndb.getOrNewPB(id)
|
||||||
info["pricing/balance"], info["pricing/balanceMeta"] = pb.value, pb.meta
|
info["pricing/balance"], info["pricing/balanceMeta"] = pb.value, pb.meta
|
||||||
info["priority"] = pb.value != 0
|
info["priority"] = pb.value.base != 0
|
||||||
}
|
}
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien
|
||||||
setFactor(&negFactors.requestFactor)
|
setFactor(&negFactors.requestFactor)
|
||||||
case !defParams && name == "capacity":
|
case !defParams && name == "capacity":
|
||||||
if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity {
|
if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity {
|
||||||
err = api.server.clientPool.setCapacity(client, uint64(capacity))
|
_, _, err = api.server.clientPool.setCapacity(client.id, client.freeID, uint64(capacity), 0, true)
|
||||||
// Don't have to call factor update explicitly. It's already done
|
// Don't have to call factor update explicitly. It's already done
|
||||||
// in setCapacity function.
|
// in setCapacity function.
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -184,7 +184,7 @@ func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[str
|
||||||
if client != nil {
|
if client != nil {
|
||||||
update, err := api.setParams(params, client, nil, nil)
|
update, err := api.setParams(params, client, nil, nil)
|
||||||
if update {
|
if update {
|
||||||
client.updatePriceFactors()
|
updatePriceFactors(&client.balanceTracker, client.posFactors, client.negFactors, client.capacity)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -352,3 +352,213 @@ func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
|
||||||
}
|
}
|
||||||
return api.backend.oracle.Contract().ContractAddr().Hex(), nil
|
return api.backend.oracle.Contract().ContractAddr().Hex(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrivateLespayAPI provides an API to use the LESpay commands of either the local or a remote server
|
||||||
|
type PrivateLespayAPI struct {
|
||||||
|
peerSet *peerSet
|
||||||
|
clientHandler *clientHandler
|
||||||
|
dht *discv5.Network
|
||||||
|
tokenSale *tokenSale
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateLespayAPI creates a new LESPAY API.
|
||||||
|
func NewPrivateLespayAPI(peerSet *peerSet, clientHandler *clientHandler, dht *discv5.Network, tokenSale *tokenSale) *PrivateLespayAPI {
|
||||||
|
return &PrivateLespayAPI{
|
||||||
|
peerSet: peerSet,
|
||||||
|
clientHandler: clientHandler,
|
||||||
|
dht: dht,
|
||||||
|
tokenSale: tokenSale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCall sends an encoded command to either the local or a remote server and returns the encoded reply
|
||||||
|
//
|
||||||
|
// Note: nodeStr can represent either the node ID of a connected node or the full enode of any remote node.
|
||||||
|
// If remote is true then the command is sent to the specified node. It is sent through LES if it was specified
|
||||||
|
// with node ID, throush UDP talk otherwise.
|
||||||
|
// If remote is false then the command is executed locally, with the specified remote node assumed as sender.
|
||||||
|
func (api *PrivateLespayAPI) makeCall(ctx context.Context, remote bool, nodeStr string, cmd []byte) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
id enode.ID
|
||||||
|
freeID string
|
||||||
|
peer *peer
|
||||||
|
node *enode.Node
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if nodeStr != "" {
|
||||||
|
if id, err = enode.ParseID(nodeStr); err == nil {
|
||||||
|
if peer = api.peerSet.Peer(peerIdToString(id)); peer == nil {
|
||||||
|
return nil, errors.New("peer not connected")
|
||||||
|
}
|
||||||
|
freeID = peer.freeClientId()
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
if node, err = enode.Parse(enode.ValidSchemes, nodeStr); err == nil {
|
||||||
|
id = node.ID()
|
||||||
|
freeID = node.IP().String()
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remote {
|
||||||
|
var (
|
||||||
|
reply []byte
|
||||||
|
cancelFn func() bool
|
||||||
|
)
|
||||||
|
delivered := make(chan struct{})
|
||||||
|
if peer != nil {
|
||||||
|
// remote call to a connected peer through LES
|
||||||
|
if api.clientHandler == nil {
|
||||||
|
return nil, errors.New("client handler not available")
|
||||||
|
}
|
||||||
|
cancelFn = api.clientHandler.makeLespayCall(peer, cmd, func(r []byte, delay uint) bool {
|
||||||
|
reply = r
|
||||||
|
close(delivered)
|
||||||
|
return reply != nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// remote call through UDP TALK
|
||||||
|
if api.dht == nil {
|
||||||
|
return nil, errors.New("UDP DHT not available")
|
||||||
|
}
|
||||||
|
cancelFn = api.dht.SendTalkRequest(node, "lespay", [][]byte{cmd}, func(payload interface{}, delay uint) bool {
|
||||||
|
if replies, ok := payload.([]interface{}); ok && len(replies) == 1 {
|
||||||
|
reply, _ = replies[0].([]byte)
|
||||||
|
}
|
||||||
|
close(delivered)
|
||||||
|
return reply != nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
cancelFn()
|
||||||
|
return nil, errors.New("timeout")
|
||||||
|
case <-ctx.Done():
|
||||||
|
cancelFn()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-delivered:
|
||||||
|
if len(reply) == 0 {
|
||||||
|
return nil, errors.New("unknown command")
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if api.tokenSale == nil {
|
||||||
|
return nil, errors.New("token sale module not available")
|
||||||
|
}
|
||||||
|
// execute call locally
|
||||||
|
return api.tokenSale.runCommand(cmd, id, freeID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection checks whether it is possible with the current balance levels to establish
|
||||||
|
// requested connection or capacity change and then stay connected for the given amount
|
||||||
|
// of time. If it is possible and setCap is also true then the client is activated of the
|
||||||
|
// capacity change is performed. If not then returns how many tokens are missing and how
|
||||||
|
// much that would currently cost using the specified payment module(s).
|
||||||
|
func (api *PrivateLespayAPI) Connection(ctx context.Context, remote bool, node string, requestedCapacity, stayConnected uint64, paymentModule []string, setCap bool) (results tsConnectionResults, err error) {
|
||||||
|
params := tsConnectionParams{requestedCapacity, stayConnected, paymentModule, setCap}
|
||||||
|
enc, _ := rlp.EncodeToBytes(¶ms)
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsConnection}, enc...))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deposit credits a payment on the sender's account using the specified payment module
|
||||||
|
func (api *PrivateLespayAPI) Deposit(ctx context.Context, remote bool, node, paymentModule, proofOfPayment string) (results tsDepositResults, err error) {
|
||||||
|
var proof []byte
|
||||||
|
if proof, err = hexutil.Decode(proofOfPayment); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params := tsDepositParams{paymentModule, proof}
|
||||||
|
enc, _ := rlp.EncodeToBytes(¶ms)
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsDeposit}, enc...))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuyTokens tries to convert the permanent balance (nominated in the server's preferred
|
||||||
|
// currency, PC) to service tokens. If spendAll is true then it sells the maxSpend amount
|
||||||
|
// of PC coins if the received service token amount is at least minReceive. If spendAll is
|
||||||
|
// false then is buys minReceive amount of tokens if it does not cost more than maxSpend
|
||||||
|
// amount of PC coins.
|
||||||
|
// if relative is true then maxSpend and minReceive are specified relative to their current
|
||||||
|
// balances. In this case maxSpend represents the amount under which the PC balance should
|
||||||
|
// not go and minReceive represents the amount the service token balance should reach.
|
||||||
|
// This mode is useful when actual conversion is intended to happen and the sender has to
|
||||||
|
// retry the command after not receiving a reply previously. In this case the sender cannot
|
||||||
|
// be sure whether the conversion has already happened or not. If relative is true then it
|
||||||
|
// is impossible to do a conversion twice. In exchange the sender needs to know its current
|
||||||
|
// balances (which it probably does if it has made a previous call to just ask the current price).
|
||||||
|
func (api *PrivateLespayAPI) BuyTokens(ctx context.Context, remote bool, node string, maxSpend, minReceive uint64, relative, spendAll bool) (results tsBuyTokensResults, err error) {
|
||||||
|
params := tsBuyTokensParams{maxSpend, minReceive, relative, spendAll}
|
||||||
|
enc, _ := rlp.EncodeToBytes(¶ms)
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsBuyTokens}, enc...))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SellTokens tries to convert service tokens to permanent balance (nominated in the server's
|
||||||
|
// preferred currency, PC). Parameters work similarly to BuyTokens.
|
||||||
|
func (api *PrivateLespayAPI) SellTokens(ctx context.Context, remote bool, node string, maxSell, minRefund uint64, relative, sellAll bool) (results tsSellTokensResults, err error) {
|
||||||
|
params := tsSellTokensParams{maxSell, minRefund, relative, sellAll}
|
||||||
|
enc, _ := rlp.EncodeToBytes(¶ms)
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsSellTokens}, enc...))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBalance returns the current PC balance and service token balance
|
||||||
|
func (api *PrivateLespayAPI) GetBalance(ctx context.Context, remote bool, node string) (results tsGetBalanceResults, err error) {
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, []byte{tsGetBalance})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info returns general information about the server, including version info of the
|
||||||
|
// lespay command set, supported payment modules and token expiration time constant
|
||||||
|
func (api *PrivateLespayAPI) Info(ctx context.Context, remote bool, node string) (results tsInfoApiResults, err error) {
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, []byte{tsInfo})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiverInfo returns information about the specified payment receiver(s) if supported
|
||||||
|
func (api *PrivateLespayAPI) ReceiverInfo(ctx context.Context, remote bool, node string, receiverIDs []string) (results tsReceiverInfoApiResults, err error) {
|
||||||
|
params := tsReceiverInfoParams(receiverIDs)
|
||||||
|
enc, _ := rlp.EncodeToBytes(¶ms)
|
||||||
|
var resEnc []byte
|
||||||
|
resEnc, err = api.makeCall(ctx, remote, node, append([]byte{tsReceiverInfo}, enc...))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = rlp.DecodeBytes(resEnc, &results)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ type LightEthereum struct {
|
||||||
lesCommons
|
lesCommons
|
||||||
|
|
||||||
peers *serverPeerSet
|
peers *serverPeerSet
|
||||||
|
srvr *p2p.Server
|
||||||
reqDist *requestDistributor
|
reqDist *requestDistributor
|
||||||
retriever *retrieveManager
|
retriever *retrieveManager
|
||||||
odr *LesOdr
|
odr *LesOdr
|
||||||
|
|
@ -208,6 +209,12 @@ func (s *LightEthereum) APIs() []rpc.API {
|
||||||
Service: NewPrivateLightAPI(&s.lesCommons),
|
Service: NewPrivateLightAPI(&s.lesCommons),
|
||||||
Public: false,
|
Public: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Namespace: "lespay",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateLespayAPI(s.lesCommons.peers, s.handler, s.srvr.DiscV5, nil),
|
||||||
|
Public: false,
|
||||||
|
},
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,6 +244,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol {
|
||||||
// light ethereum protocol implementation.
|
// light ethereum protocol implementation.
|
||||||
func (s *LightEthereum) Start(srvr *p2p.Server) error {
|
func (s *LightEthereum) Start(srvr *p2p.Server) error {
|
||||||
log.Warn("Light client mode is an experimental feature")
|
log.Warn("Light client mode is an experimental feature")
|
||||||
|
s.srvr = srvr
|
||||||
|
|
||||||
// Start bloom request workers.
|
// Start bloom request workers.
|
||||||
s.wg.Add(bloomServiceThreads)
|
s.wg.Add(bloomServiceThreads)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue