diff --git a/les/tokensale.go b/les/tokensale.go new file mode 100644 index 0000000000..209f3a6fa2 --- /dev/null +++ b/les/tokensale.go @@ -0,0 +1,409 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "fmt" + "io" + "math" + "sync" + "time" + + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +const basePriceTC = time.Hour * 10 + +type paymentReceiver interface { + info() []byte + receivePayment(from enode.ID, proofOfPayment, oldMeta []byte) (value uint64, newMeta []byte, err error) + requestPayment(from enode.ID, value uint64, meta []byte) uint64 +} + +type tokenSale struct { + lock sync.Mutex + clientPool *clientPool + stopCh chan struct{} + receivers map[string]paymentReceiver + basePrice, minBasePrice float64 +} + +func newTokenSale(clientPool *clientPool, minBasePrice float64) *tokenSale { + t := &tokenSale{ + clientPool: clientPool, + receivers: make(map[string]paymentReceiver), + basePrice: minBasePrice, + minBasePrice: minBasePrice, + stopCh: make(chan struct{}), + } + go func() { + for { + select { + case <-time.After(time.Second * 10): + t.lock.Lock() + cost, ok := t.tokenCost(1) + if cost > t.basePrice*10 || !ok { + cost = t.basePrice * 10 + } + t.basePrice += (cost - t.basePrice) * float64(time.Second*10) / float64(basePriceTC) + if t.basePrice < minBasePrice { + t.basePrice = minBasePrice + } + t.lock.Unlock() + case <-t.stopCh: + return + } + } + }() + return t +} + +func (t *tokenSale) stop() { + close(t.stopCh) +} + +func (t *tokenSale) tokenCost(buyAmount uint64) (float64, bool) { + tokenLimit := t.clientPool.totalTokenLimit() + tokenAmount := t.clientPool.totalTokenAmount() + if tokenAmount+buyAmount >= tokenLimit { + return 0, false + } + r := float64(tokenAmount) / float64(tokenLimit) + b := float64(buyAmount) / float64(tokenLimit) + var relCost float64 + if r < 0.5 { + if r+b <= 0.5 { + relCost = b * (r + r + b) + b = 0 + } else { + relCost = (0.5 - r) * (r + 0.5) + b = r + b - 0.5 + r = 0.5 + } + } + if b > 0 { + l := 1 - r + if l < 1e-10 { + return 0, false + } + l = -b / l + if l < -1+1e-10 { + return 0, false + } + relCost += -math.Log1p(l) / 2 + + } + return t.basePrice * float64(tokenLimit) * relCost, true +} + +func (t *tokenSale) tokensFor(maxCost uint64) uint64 { + tokenLimit := t.clientPool.totalTokenLimit() + tokenAmount := t.clientPool.totalTokenAmount() + if tokenLimit <= tokenAmount { + return 0 + } + r := float64(tokenAmount) / float64(tokenLimit) + c := float64(maxCost) / (t.basePrice * float64(tokenLimit)) + var relTokens float64 + if r < 0.5 { + relTokens = math.Sqrt(r*r+c) - r + if r+relTokens <= 0.5 { + c = 0 + } else { + relTokens = 0.5 - r + c -= (0.5 - r) * (r + 0.5) + } + } + if c > 0 { + relTokens += -math.Expm1(-2*c) * (1 - r) + } + return uint64(relTokens * float64(tokenLimit)) +} + +func (t *tokenSale) connection(id enode.ID, freeID string, requestedCapacity uint64, stayConnected time.Duration, paymentModule []string, setCap bool) (availableCapacity, tokenBalance, tokensMissing, pcBalance, pcMissing uint64, paymentRequired []uint64, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + tokensMissing, availableCapacity, err = t.clientPool.setCapacityLocked(id, freeID, requestedCapacity, stayConnected, setCap) + pb := t.clientPool.getPosBalance(id) + tokenBalance = pb.value + var meta tokenSaleMeta + if err := rlp.DecodeBytes([]byte(pb.meta), &meta); err == nil { + pcBalance = meta.pcBalance + } + if tokensMissing == 0 { + return + } + tokenLimit := t.clientPool.totalTokenLimit() + tokenAmount := t.clientPool.totalTokenAmount() + if tokenLimit <= tokenAmount || tokenLimit-tokenAmount <= tokensMissing { + pcMissing = math.MaxUint64 + } else { + tokensAvailable := tokenLimit - tokenAmount + pcr := -math.Log(float64(tokensAvailable-tokensMissing)/float64(tokensAvailable)) * t.basePrice + if pcr > 0 { + if pcr > maxBalance { + pcMissing = math.MaxUint64 + } else { + pcMissing = uint64(pcr) + if pcMissing > maxBalance { + pcMissing = math.MaxUint64 + } else { + if pcMissing > pcBalance { + pcMissing -= pcBalance + } else { + pcMissing = 0 + } + } + } + } + } + if pcMissing == 0 { + return + } + paymentRequired = make([]uint64, len(paymentModule)) + for i, recID := range paymentModule { + if rec, ok := t.receivers[recID]; !ok || pcMissing == math.MaxUint64 { + paymentRequired[i] = math.MaxUint64 + } else { + paymentRequired[i] = rec.requestPayment(id, pcMissing, meta.receiverMeta[recID]) + } + } + return +} + +func (t *tokenSale) deposit(id enode.ID, paymentModule string, proofOfPayment []byte) (pcValue, pcBalance uint64, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + pb := t.clientPool.getPosBalance(id) + var meta tokenSaleMeta + if err := rlp.DecodeBytes([]byte(pb.meta), &meta); err == nil { + pcBalance = meta.pcBalance + } + + pm := t.receivers[paymentModule] + if pm == nil { + return 0, pcBalance, fmt.Errorf("Unknown payment receiver '%s'", paymentModule) + } + pcValue, meta.receiverMeta[paymentModule], err = pm.receivePayment(id, proofOfPayment, meta.receiverMeta[paymentModule]) + if err != nil { + return 0, pcBalance, err + } + pcBalance += pcValue + meta.pcBalance = pcBalance + metaEnc, _ := rlp.EncodeToBytes(&meta) + t.clientPool.addBalance(id, 0, string(metaEnc)) + return +} + +func (t *tokenSale) buyTokens(id enode.ID, maxSpend, minReceive uint64, spendAll bool) (pcBalance, tokenBalance, spend, receive uint64, success bool) { + t.lock.Lock() + defer t.lock.Unlock() + + pb := t.clientPool.getPosBalance(id) + tokenBalance = pb.value + var meta tokenSaleMeta + if err := rlp.DecodeBytes([]byte(pb.meta), &meta); err == nil { + pcBalance = meta.pcBalance + } + + if maxSpend > pcBalance { + maxSpend = pcBalance + } + if spendAll { + spend = maxSpend + receive = t.tokensFor(spend) + success = receive >= minReceive + } else { + receive = minReceive + if cost, ok := t.tokenCost(receive); ok { + spend = uint64(cost) + } else { + spend = math.MaxUint64 + } + success = spend <= maxSpend + } + if success { + pcBalance -= spend + tokenBalance += receive + meta.pcBalance = pcBalance + metaEnc, _ := rlp.EncodeToBytes(&meta) + t.clientPool.addBalance(id, int64(receive), string(metaEnc)) + } + return +} + +func (t *tokenSale) paymentInfo(paymentModule []string) [][]byte { + t.lock.Lock() + defer t.lock.Unlock() + + res := make([][]byte, len(paymentModule)) + for i, id := range paymentModule { + if rec, ok := t.receivers[id]; ok { + res[i] = rec.info() + } + } + return res +} + +type tokenSaleMeta struct { + pcBalance uint64 + receiverMeta map[string][]byte +} + +type receiverMetaEnc struct { + Id string + Meta []byte +} + +type tokenSaleMetaEnc struct { + Id string + Version uint + PcBalance uint64 + Receivers []receiverMetaEnc +} + +// EncodeRLP implements rlp.Encoder +func (t *tokenSaleMeta) EncodeRLP(w io.Writer) error { + receivers := make([]receiverMetaEnc, len(t.receiverMeta)) + i := 0 + for id, meta := range t.receiverMeta { + receivers[i] = receiverMetaEnc{id, meta} + i++ + } + return rlp.Encode(w, tokenSaleMetaEnc{ + Id: "tokenSale", + Version: 1, + PcBalance: t.pcBalance, + Receivers: receivers, + }) +} + +// DecodeRLP implements rlp.Decoder +func (t *tokenSaleMeta) DecodeRLP(s *rlp.Stream) error { + var e tokenSaleMetaEnc + if err := s.Decode(&e); err != nil { + return err + } + if e.Id != "tokenSale" || e.Version != 1 { + return fmt.Errorf("Unknown balance meta format '%s' version %d", e.Id, e.Version) + } + t.receiverMeta = make(map[string][]byte) + t.pcBalance = e.PcBalance + for _, r := range e.Receivers { + t.receiverMeta[r.Id] = r.Meta + } + return nil +} + +const ( + tsInfo = iota + tsDeposit + tsBuyTokens + tsConnection +) + +/*func (t *tokenSale) connection(id enode.ID, freeID string, requestedCapacity uint64, stayConnected time.Duration, paymentModule []string, setCap bool) (availableCapacity, tokenBalance, tokensMissing, pcBalance, pcMissing uint64, paymentRequired []uint64, err error) { +func (t *tokenSale) deposit(id enode.ID, paymentModule string, proofOfPayment []byte) (pcValue, pcBalance uint64, err error) { +func (t *tokenSale) buyTokens(id enode.ID, maxSpend, minReceive uint64, spendAll bool) (pcBalance, tokenBalance, spend, receive uint64, success bool) { +func (t *tokenSale) paymentInfo(paymentModule []string) map[string][]byte {*/ + +type ( + tsDepositParams struct { + PaymentModule string + ProofOfPayment []byte + } + tsDepositResults struct { + PcValue, PcBalance uint64 + Err string + } + tsBuyTokensParams struct { + MaxSpend, MinReceive uint64 + SpendAll bool + } + tsBuyTokensResults struct { + PcBalance, TokenBalance, Spend, Receive uint64 + Success bool + } + tsConnectionParams struct { + RequestedCapacity, StayConnected uint64 + PaymentModule []string + SetCap bool + } + tsConnectionResults struct { + AvailableCapacity, TokenBalance, TokensMissing, PcBalance, PcMissing uint64 + PaymentRequired []uint64 + Err string + } +) + +func (t *tokenSale) runCommand(cmd []byte, id enode.ID, freeID string) []byte { + var res []byte + switch cmd[0] { + case tsInfo: + var ( + params []string + results [][]byte + ) + if err := rlp.DecodeBytes(cmd[1:], ¶ms); err == nil { + results = t.paymentInfo(params) + res, _ = rlp.EncodeToBytes(&results) + } + case tsDeposit: + var ( + params tsDepositParams + results tsDepositResults + ) + if err := rlp.DecodeBytes(cmd[1:], ¶ms); err == nil { + results.PcValue, results.PcBalance, err = t.deposit(id, params.PaymentModule, params.ProofOfPayment) + results.Err = err.Error() + res, _ = rlp.EncodeToBytes(&results) + } + case tsBuyTokens: + var ( + params tsBuyTokensParams + results tsBuyTokensResults + ) + if err := rlp.DecodeBytes(cmd[1:], ¶ms); err == nil { + results.PcBalance, results.TokenBalance, results.Spend, results.Receive, results.Success = + t.buyTokens(id, params.MaxSpend, params.MinReceive, params.SpendAll) + res, _ = rlp.EncodeToBytes(&results) + } + case tsConnection: + var ( + params tsConnectionParams + results tsConnectionResults + ) + if err := rlp.DecodeBytes(cmd[1:], ¶ms); err == nil { + results.AvailableCapacity, results.TokenBalance, results.TokensMissing, results.PcBalance, results.PcMissing, results.PaymentRequired, err = + t.connection(id, freeID, params.RequestedCapacity, time.Duration(params.StayConnected)*time.Second, params.PaymentModule, params.SetCap) + results.Err = err.Error() + res, _ = rlp.EncodeToBytes(&results) + } + } + return res +} + +func (t *tokenSale) runCommands(cmds [][]byte, id enode.ID, freeID string) [][]byte { + res := make([][]byte, len(cmds)) + for i, cmd := range cmds { + res[i] = t.runCommand(cmd, id, freeID) + } + return res +}