mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
* add bzz.syncEnabled (hive.syncEnabled -> testapi/SyncEnabled | bzznosync param)) triggers nil sync State request to peer * syncEnabled false then historical items are not delivered * add swap initial scenarios, no funds, no retrieval; chequebook autodeployment; available funds -> retrieval * remove -ipcexp option from swarm script * tweaks to swarm/cmd files * add Balance to chequebook api * set chequebook balance from blockchain * fix chequebook tests after balance check * log balance when chequebook created * fix state setting in ethereum backend * add balanceAt to backend
271 lines
8.7 KiB
Go
271 lines
8.7 KiB
Go
package swap
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/chequebook"
|
|
"github.com/ethereum/go-ethereum/common/swap"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
"github.com/ethereum/go-ethereum/logger/glog"
|
|
)
|
|
|
|
// SwAP Swarm Accounting Protocol with
|
|
// SWAP^2 Strategies of Withholding Automatic Payments
|
|
// SWAP^3 Accreditation: payment via credit SWAP
|
|
// using chequebook pkg for delayed payments
|
|
// default parameters
|
|
|
|
var (
|
|
autoCashInterval = 300 * time.Second // default interval for autocash
|
|
autoCashThreshold = big.NewInt(50000000000000) // threshold that triggers autocash (wei)
|
|
autoDepositInterval = 300 * time.Second // default interval for autocash
|
|
autoDepositThreshold = big.NewInt(50000000000000) // threshold that triggers autodeposit (wei)
|
|
autoDepositBuffer = big.NewInt(100000000000000) // buffer that is surplus for fork protection etc (wei)
|
|
buyAt = big.NewInt(20000000000) // maximum chunk price host is willing to pay (wei)
|
|
sellAt = big.NewInt(20000000000) // minimum chunk price host requires (wei)
|
|
payAt = 100 // threshold that triggers payment request (units)
|
|
dropAt = 10000 // threshold that triggers disconnect (units)
|
|
|
|
maxRetries = 5
|
|
)
|
|
|
|
var (
|
|
retryInterval = 10 * time.Second
|
|
)
|
|
|
|
type SwapParams struct {
|
|
*swap.Params
|
|
*PayProfile
|
|
}
|
|
|
|
type SwapProfile struct {
|
|
*swap.Profile
|
|
*PayProfile
|
|
}
|
|
|
|
type PayProfile struct {
|
|
PublicKey string // check againsst signature of promise
|
|
Contract common.Address // address of chequebook contract
|
|
Beneficiary common.Address // recipient address for swarm sales revenue
|
|
privateKey *ecdsa.PrivateKey `json:"-"`
|
|
publicKey *ecdsa.PublicKey `json:"-"`
|
|
owner common.Address
|
|
chbook *chequebook.Chequebook `json:"-"`
|
|
backend chequebook.Backend
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
func DefaultSwapParams(contract common.Address, prvkey *ecdsa.PrivateKey) *SwapParams {
|
|
pubkey := &prvkey.PublicKey
|
|
return &SwapParams{
|
|
PayProfile: &PayProfile{
|
|
PublicKey: common.ToHex(crypto.FromECDSAPub(pubkey)),
|
|
Contract: contract,
|
|
Beneficiary: crypto.PubkeyToAddress(*pubkey),
|
|
privateKey: prvkey,
|
|
publicKey: pubkey,
|
|
owner: crypto.PubkeyToAddress(*pubkey),
|
|
},
|
|
Params: &swap.Params{
|
|
Profile: &swap.Profile{
|
|
BuyAt: buyAt,
|
|
SellAt: sellAt,
|
|
PayAt: uint(payAt),
|
|
DropAt: uint(dropAt),
|
|
},
|
|
Strategy: &swap.Strategy{
|
|
AutoCashInterval: autoCashInterval,
|
|
AutoCashThreshold: autoCashThreshold,
|
|
AutoDepositInterval: autoDepositInterval,
|
|
AutoDepositThreshold: autoDepositThreshold,
|
|
AutoDepositBuffer: autoDepositBuffer,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// swap constructor, parameters
|
|
// * global chequebook, assume deployed service and
|
|
// * the balance is at buffer.
|
|
// swap.Add(n) called in netstore
|
|
// n > 0 called when sending chunks = receiving retrieve requests
|
|
// OR sending cheques.
|
|
// n < 0 called when receiving chunks = receiving delivery responses
|
|
// OR receiving cheques.
|
|
|
|
func NewSwap(local *SwapParams, remote *SwapProfile, proto swap.Protocol) (self *swap.Swap, err error) {
|
|
|
|
// check if remote chequebook is valid
|
|
// insolvent chequebooks suicide so will signal as invalid
|
|
// TODO: monitoring a chequebooks events
|
|
var in *chequebook.Inbox
|
|
err = chequebook.Validate(remote.Contract, local.backend)
|
|
if err != nil {
|
|
glog.V(logger.Info).Infof("[BZZ] SWAP invalid contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err)
|
|
} else {
|
|
// remote contract valid, create inbox
|
|
in, err = chequebook.NewInbox(remote.Contract, local.owner, local.Beneficiary, crypto.ToECDSAPub(common.FromHex(remote.PublicKey)), local.backend)
|
|
if err != nil {
|
|
glog.V(logger.Warn).Infof("[BZZ] SWAP unable to set up inbox for chequebook contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err)
|
|
}
|
|
}
|
|
|
|
// cheque if local chequebook contract is valid
|
|
var out *chequebook.Outbox
|
|
err = chequebook.Validate(local.Contract, local.backend)
|
|
if err != nil {
|
|
glog.V(logger.Warn).Infof("[BZZ] SWAP unable to set up outbox for peer %v: chequebook contract (owner: %v): %v)", proto, local.owner.Hex(), err)
|
|
} else {
|
|
out = chequebook.NewOutbox(local.Chequebook(), remote.Beneficiary)
|
|
}
|
|
|
|
pm := swap.Payment{
|
|
In: in,
|
|
Out: out,
|
|
Buys: out != nil,
|
|
Sells: in != nil,
|
|
}
|
|
self, err = swap.New(local.Params, pm, proto)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// remote profile given (first) in handshake
|
|
self.SetRemote(remote.Profile)
|
|
var buy, sell string
|
|
if self.Buys {
|
|
buy = "purchase from peer enabled at " + remote.SellAt.String() + " wei/chunk"
|
|
} else {
|
|
buy = "purchase from peer disabled"
|
|
}
|
|
if self.Sells {
|
|
sell = "selling to peer enabled at " + local.SellAt.String() + " wei/chunk"
|
|
} else {
|
|
sell = "selling to peer disabled"
|
|
}
|
|
glog.V(logger.Warn).Infof("[BZZ] SWAP arrangement with <%v>: %v; %v)", proto, buy, sell)
|
|
|
|
return
|
|
}
|
|
|
|
func (self *SwapParams) Chequebook() *chequebook.Chequebook {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
return self.chbook
|
|
}
|
|
|
|
func (self *SwapParams) PrivateKey() *ecdsa.PrivateKey {
|
|
return self.privateKey
|
|
}
|
|
|
|
func (self *SwapParams) PublicKey() *ecdsa.PublicKey {
|
|
return self.publicKey
|
|
}
|
|
func (self *SwapParams) SetKey(prvkey *ecdsa.PrivateKey) {
|
|
self.privateKey = prvkey
|
|
self.publicKey = &prvkey.PublicKey
|
|
}
|
|
|
|
const (
|
|
confirmationInterval = 60000000000
|
|
timeout = 30000000000 // 30 sec
|
|
)
|
|
|
|
// setChequebook(path, backend) wraps the
|
|
// chequebook initialiser and sets up autoDeposit to cover spending.
|
|
func (self *SwapParams) SetChequebook(path string, backend chequebook.Backend) (done chan bool, err error) {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
var valid bool
|
|
done = make(chan bool)
|
|
self.backend = backend
|
|
err = chequebook.Validate(self.Contract, backend)
|
|
if err != nil {
|
|
owner := crypto.PubkeyToAddress(*(self.publicKey))
|
|
go self.deployChequebook(owner, path, done)
|
|
} else {
|
|
valid = true
|
|
go func() {
|
|
done <- false
|
|
close(done)
|
|
}()
|
|
}
|
|
if valid {
|
|
err = self.newChequebookFromContract(path, backend)
|
|
return done, err
|
|
}
|
|
return done, nil
|
|
}
|
|
|
|
func (self *SwapParams) deployChequebook(owner common.Address, path string, done chan bool) {
|
|
var timer = time.NewTimer(0).C
|
|
retries := 0
|
|
var err error
|
|
var valid bool
|
|
OUT:
|
|
for {
|
|
select {
|
|
case <-timer:
|
|
// this is blocking
|
|
glog.V(logger.Info).Infof("[BZZ] SWAP Deploying new chequebook (owner: %v)", owner.Hex())
|
|
var contract common.Address
|
|
contract, err = chequebook.Deploy(owner, self.backend, self.AutoDepositBuffer, confirmationInterval, timeout)
|
|
if err != nil {
|
|
glog.V(logger.Info).Infof("[BZZ] SWAP unable to deploy new chequebook: %v...retrying in %v", err, retryInterval)
|
|
if retries >= maxRetries {
|
|
glog.V(logger.Info).Infof("[BZZ] SWAP unable to deploy new chequebook: giving up after %v retries", retries)
|
|
break OUT
|
|
}
|
|
retries++
|
|
timer = time.NewTicker(retryInterval).C
|
|
} else {
|
|
// need to save config at this point
|
|
self.lock.Lock()
|
|
self.Contract = contract
|
|
err = self.newChequebookFromContract(path, self.backend)
|
|
if err != nil {
|
|
glog.V(logger.Info).Infof("[BZZ] SWAP error initialising cheque book (owner: %v)", owner.Hex())
|
|
}
|
|
self.lock.Unlock()
|
|
valid = true
|
|
break OUT
|
|
}
|
|
}
|
|
}
|
|
done <- valid
|
|
close(done)
|
|
}
|
|
|
|
// initialise the chequebook from a persisted json file or create a new one
|
|
// caller holds the lock
|
|
func (self *SwapParams) newChequebookFromContract(path string, backend chequebook.Backend) error {
|
|
|
|
hexkey := common.Bytes2Hex(self.Contract.Bytes())
|
|
err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create directory for chequebooks: %v", err)
|
|
}
|
|
|
|
chbookpath := filepath.Join(path, "chequebooks", hexkey+".json")
|
|
self.chbook, err = chequebook.LoadChequebook(chbookpath, self.privateKey, backend, true)
|
|
|
|
if err != nil {
|
|
self.chbook, err = chequebook.NewChequebook(chbookpath, self.Contract, self.privateKey, backend)
|
|
if err != nil {
|
|
glog.V(logger.Warn).Infof("[BZZ] SWAP unable to initialise chequebook (owner: %v): %v", self.owner.Hex(), err)
|
|
return fmt.Errorf("unable to initialise chequebook (owner: %v): %v", self.owner.Hex(), err)
|
|
}
|
|
}
|
|
|
|
self.chbook.AutoDeposit(self.AutoDepositInterval, self.AutoDepositThreshold, self.AutoDepositBuffer)
|
|
glog.V(logger.Info).Infof("[BZZ] SWAP auto deposit ON for %v -> %v: interval = %v, threshold = %v, buffer = %v)", crypto.PubkeyToAddress(*(self.publicKey)).Hex()[:8], self.Contract.Hex()[:8], self.AutoDepositInterval, self.AutoDepositThreshold, self.AutoDepositBuffer)
|
|
|
|
return nil
|
|
}
|