github.com/FUSIONFoundation/efsn@v3.6.2-0.20200916075423-dbb5dd5d2cc7+incompatible/swarm/services/swap/swap.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package swap 18 19 import ( 20 "context" 21 "crypto/ecdsa" 22 "errors" 23 "fmt" 24 "math/big" 25 "os" 26 "path/filepath" 27 "sync" 28 "time" 29 30 "github.com/FusionFoundation/efsn/accounts/abi/bind" 31 "github.com/FusionFoundation/efsn/common" 32 "github.com/FusionFoundation/efsn/contracts/chequebook" 33 "github.com/FusionFoundation/efsn/contracts/chequebook/contract" 34 "github.com/FusionFoundation/efsn/core/types" 35 "github.com/FusionFoundation/efsn/crypto" 36 "github.com/FusionFoundation/efsn/swarm/log" 37 "github.com/FusionFoundation/efsn/swarm/services/swap/swap" 38 ) 39 40 // SwAP Swarm Accounting Protocol with 41 // SWAP^2 Strategies of Withholding Automatic Payments 42 // SWAP^3 Accreditation: payment via credit SWAP 43 // using chequebook pkg for delayed payments 44 // default parameters 45 46 var ( 47 autoCashInterval = 300 * time.Second // default interval for autocash 48 autoCashThreshold = big.NewInt(50000000000000) // threshold that triggers autocash (wei) 49 autoDepositInterval = 300 * time.Second // default interval for autocash 50 autoDepositThreshold = big.NewInt(50000000000000) // threshold that triggers autodeposit (wei) 51 autoDepositBuffer = big.NewInt(100000000000000) // buffer that is surplus for fork protection etc (wei) 52 buyAt = big.NewInt(20000000000) // maximum chunk price host is willing to pay (wei) 53 sellAt = big.NewInt(20000000000) // minimum chunk price host requires (wei) 54 payAt = 100 // threshold that triggers payment {request} (units) 55 dropAt = 10000 // threshold that triggers disconnect (units) 56 ) 57 58 const ( 59 chequebookDeployRetries = 5 60 chequebookDeployDelay = 1 * time.Second // delay between retries 61 ) 62 63 // LocalProfile combines a PayProfile with *swap.Params 64 type LocalProfile struct { 65 *swap.Params 66 *PayProfile 67 } 68 69 // RemoteProfile combines a PayProfile with *swap.Profile 70 type RemoteProfile struct { 71 *swap.Profile 72 *PayProfile 73 } 74 75 // PayProfile is a container for relevant chequebook and beneficiary options 76 type PayProfile struct { 77 PublicKey string // check against signature of promise 78 Contract common.Address // address of chequebook contract 79 Beneficiary common.Address // recipient address for swarm sales revenue 80 privateKey *ecdsa.PrivateKey 81 publicKey *ecdsa.PublicKey 82 owner common.Address 83 chbook *chequebook.Chequebook 84 lock sync.RWMutex 85 } 86 87 // NewDefaultSwapParams create params with default values 88 func NewDefaultSwapParams() *LocalProfile { 89 return &LocalProfile{ 90 PayProfile: &PayProfile{}, 91 Params: &swap.Params{ 92 Profile: &swap.Profile{ 93 BuyAt: buyAt, 94 SellAt: sellAt, 95 PayAt: uint(payAt), 96 DropAt: uint(dropAt), 97 }, 98 Strategy: &swap.Strategy{ 99 AutoCashInterval: autoCashInterval, 100 AutoCashThreshold: autoCashThreshold, 101 AutoDepositInterval: autoDepositInterval, 102 AutoDepositThreshold: autoDepositThreshold, 103 AutoDepositBuffer: autoDepositBuffer, 104 }, 105 }, 106 } 107 } 108 109 // Init this can only finally be set after all config options (file, cmd line, env vars) 110 // have been evaluated 111 func (lp *LocalProfile) Init(contract common.Address, prvkey *ecdsa.PrivateKey) { 112 pubkey := &prvkey.PublicKey 113 114 lp.PayProfile = &PayProfile{ 115 PublicKey: common.ToHex(crypto.FromECDSAPub(pubkey)), 116 Contract: contract, 117 Beneficiary: crypto.PubkeyToAddress(*pubkey), 118 privateKey: prvkey, 119 publicKey: pubkey, 120 owner: crypto.PubkeyToAddress(*pubkey), 121 } 122 } 123 124 // NewSwap constructor, parameters 125 // * global chequebook, assume deployed service and 126 // * the balance is at buffer. 127 // swap.Add(n) called in netstore 128 // n > 0 called when sending chunks = receiving retrieve requests 129 // OR sending cheques. 130 // n < 0 called when receiving chunks = receiving delivery responses 131 // OR receiving cheques. 132 func NewSwap(localProfile *LocalProfile, remoteProfile *RemoteProfile, backend chequebook.Backend, proto swap.Protocol) (swapInstance *swap.Swap, err error) { 133 var ( 134 ctx = context.TODO() 135 ok bool 136 in *chequebook.Inbox 137 out *chequebook.Outbox 138 ) 139 140 remotekey, err := crypto.UnmarshalPubkey(common.FromHex(remoteProfile.PublicKey)) 141 if err != nil { 142 return nil, errors.New("invalid remote public key") 143 } 144 145 // check if remoteProfile chequebook is valid 146 // insolvent chequebooks suicide so will signal as invalid 147 // TODO: monitoring a chequebooks events 148 ok, err = chequebook.ValidateCode(ctx, backend, remoteProfile.Contract) 149 if !ok { 150 log.Info(fmt.Sprintf("invalid contract %v for peer %v: %v)", remoteProfile.Contract.Hex()[:8], proto, err)) 151 } else { 152 // remoteProfile contract valid, create inbox 153 in, err = chequebook.NewInbox(localProfile.privateKey, remoteProfile.Contract, localProfile.Beneficiary, remotekey, backend) 154 if err != nil { 155 log.Warn(fmt.Sprintf("unable to set up inbox for chequebook contract %v for peer %v: %v)", remoteProfile.Contract.Hex()[:8], proto, err)) 156 } 157 } 158 159 // check if localProfile chequebook contract is valid 160 ok, err = chequebook.ValidateCode(ctx, backend, localProfile.Contract) 161 if !ok { 162 log.Warn(fmt.Sprintf("unable to set up outbox for peer %v: chequebook contract (owner: %v): %v)", proto, localProfile.owner.Hex(), err)) 163 } else { 164 out = chequebook.NewOutbox(localProfile.Chequebook(), remoteProfile.Beneficiary) 165 } 166 167 pm := swap.Payment{ 168 In: in, 169 Out: out, 170 Buys: out != nil, 171 Sells: in != nil, 172 } 173 swapInstance, err = swap.New(localProfile.Params, pm, proto) 174 if err != nil { 175 return 176 } 177 // remoteProfile profile given (first) in handshake 178 swapInstance.SetRemote(remoteProfile.Profile) 179 var buy, sell string 180 if swapInstance.Buys { 181 buy = "purchase from peer enabled at " + remoteProfile.SellAt.String() + " wei/chunk" 182 } else { 183 buy = "purchase from peer disabled" 184 } 185 if swapInstance.Sells { 186 sell = "selling to peer enabled at " + localProfile.SellAt.String() + " wei/chunk" 187 } else { 188 sell = "selling to peer disabled" 189 } 190 log.Warn(fmt.Sprintf("SWAP arrangement with <%v>: %v; %v)", proto, buy, sell)) 191 192 return 193 } 194 195 // Chequebook get's chequebook from the localProfile 196 func (lp *LocalProfile) Chequebook() *chequebook.Chequebook { 197 defer lp.lock.Unlock() 198 lp.lock.Lock() 199 return lp.chbook 200 } 201 202 // PrivateKey accessor 203 func (lp *LocalProfile) PrivateKey() *ecdsa.PrivateKey { 204 return lp.privateKey 205 } 206 207 // func (self *LocalProfile) PublicKey() *ecdsa.PublicKey { 208 // return self.publicKey 209 // } 210 211 // SetKey set's private and public key on localProfile 212 func (lp *LocalProfile) SetKey(prvkey *ecdsa.PrivateKey) { 213 lp.privateKey = prvkey 214 lp.publicKey = &prvkey.PublicKey 215 } 216 217 // SetChequebook wraps the chequebook initialiser and sets up autoDeposit to cover spending. 218 func (lp *LocalProfile) SetChequebook(ctx context.Context, backend chequebook.Backend, path string) error { 219 lp.lock.Lock() 220 swapContract := lp.Contract 221 lp.lock.Unlock() 222 223 valid, err := chequebook.ValidateCode(ctx, backend, swapContract) 224 if err != nil { 225 return err 226 } else if valid { 227 return lp.newChequebookFromContract(path, backend) 228 } 229 return lp.deployChequebook(ctx, backend, path) 230 } 231 232 // deployChequebook deploys the localProfile Chequebook 233 func (lp *LocalProfile) deployChequebook(ctx context.Context, backend chequebook.Backend, path string) error { 234 opts := bind.NewKeyedTransactor(lp.privateKey) 235 opts.Value = lp.AutoDepositBuffer 236 opts.Context = ctx 237 238 log.Info(fmt.Sprintf("Deploying new chequebook (owner: %v)", opts.From.Hex())) 239 address, err := deployChequebookLoop(opts, backend) 240 if err != nil { 241 log.Error(fmt.Sprintf("unable to deploy new chequebook: %v", err)) 242 return err 243 } 244 log.Info(fmt.Sprintf("new chequebook deployed at %v (owner: %v)", address.Hex(), opts.From.Hex())) 245 246 // need to save config at this point 247 lp.lock.Lock() 248 lp.Contract = address 249 err = lp.newChequebookFromContract(path, backend) 250 lp.lock.Unlock() 251 if err != nil { 252 log.Warn(fmt.Sprintf("error initialising cheque book (owner: %v): %v", opts.From.Hex(), err)) 253 } 254 return err 255 } 256 257 // deployChequebookLoop repeatedly tries to deploy a chequebook. 258 func deployChequebookLoop(opts *bind.TransactOpts, backend chequebook.Backend) (addr common.Address, err error) { 259 var tx *types.Transaction 260 for try := 0; try < chequebookDeployRetries; try++ { 261 if try > 0 { 262 time.Sleep(chequebookDeployDelay) 263 } 264 if _, tx, _, err = contract.DeployChequebook(opts, backend); err != nil { 265 log.Warn(fmt.Sprintf("can't send chequebook deploy tx (try %d): %v", try, err)) 266 continue 267 } 268 if addr, err = bind.WaitDeployed(opts.Context, backend, tx); err != nil { 269 log.Warn(fmt.Sprintf("chequebook deploy error (try %d): %v", try, err)) 270 continue 271 } 272 return addr, nil 273 } 274 return addr, err 275 } 276 277 // newChequebookFromContract - initialise the chequebook from a persisted json file or create a new one 278 // caller holds the lock 279 func (lp *LocalProfile) newChequebookFromContract(path string, backend chequebook.Backend) error { 280 hexkey := common.Bytes2Hex(lp.Contract.Bytes()) 281 err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm) 282 if err != nil { 283 return fmt.Errorf("unable to create directory for chequebooks: %v", err) 284 } 285 286 chbookpath := filepath.Join(path, "chequebooks", hexkey+".json") 287 lp.chbook, err = chequebook.LoadChequebook(chbookpath, lp.privateKey, backend, true) 288 289 if err != nil { 290 lp.chbook, err = chequebook.NewChequebook(chbookpath, lp.Contract, lp.privateKey, backend) 291 if err != nil { 292 log.Warn(fmt.Sprintf("unable to initialise chequebook (owner: %v): %v", lp.owner.Hex(), err)) 293 return fmt.Errorf("unable to initialise chequebook (owner: %v): %v", lp.owner.Hex(), err) 294 } 295 } 296 297 lp.chbook.AutoDeposit(lp.AutoDepositInterval, lp.AutoDepositThreshold, lp.AutoDepositBuffer) 298 log.Info(fmt.Sprintf("auto deposit ON for %v -> %v: interval = %v, threshold = %v, buffer = %v)", crypto.PubkeyToAddress(*(lp.publicKey)).Hex()[:8], lp.Contract.Hex()[:8], lp.AutoDepositInterval, lp.AutoDepositThreshold, lp.AutoDepositBuffer)) 299 300 return nil 301 }