go-ethereum/swarm/services/swap/swap.go
zelig 5714724bab swap integration test
* 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
2016-03-22 17:53:43 +07:00

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
}