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