github.com/janotchain/janota@v0.0.0-20220824112012-93ea4c5dee78/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 "fmt" 23 "math/big" 24 "os" 25 "path/filepath" 26 "sync" 27 "time" 28 29 "github.com/ethereum/go-ethereum/accounts/abi/bind" 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/contracts/chequebook" 32 "github.com/ethereum/go-ethereum/contracts/chequebook/contract" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/crypto" 35 "github.com/ethereum/go-ethereum/log" 36 "github.com/ethereum/go-ethereum/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 func DefaultSwapParams(contract common.Address, prvkey *ecdsa.PrivateKey) *SwapParams { 84 pubkey := &prvkey.PublicKey 85 return &SwapParams{ 86 PayProfile: &PayProfile{ 87 PublicKey: common.ToHex(crypto.FromECDSAPub(pubkey)), 88 Contract: contract, 89 Beneficiary: crypto.PubkeyToAddress(*pubkey), 90 privateKey: prvkey, 91 publicKey: pubkey, 92 owner: crypto.PubkeyToAddress(*pubkey), 93 }, 94 Params: &swap.Params{ 95 Profile: &swap.Profile{ 96 BuyAt: buyAt, 97 SellAt: sellAt, 98 PayAt: uint(payAt), 99 DropAt: uint(dropAt), 100 }, 101 Strategy: &swap.Strategy{ 102 AutoCashInterval: autoCashInterval, 103 AutoCashThreshold: autoCashThreshold, 104 AutoDepositInterval: autoDepositInterval, 105 AutoDepositThreshold: autoDepositThreshold, 106 AutoDepositBuffer: autoDepositBuffer, 107 }, 108 }, 109 } 110 } 111 112 // swap constructor, parameters 113 // * global chequebook, assume deployed service and 114 // * the balance is at buffer. 115 // swap.Add(n) called in netstore 116 // n > 0 called when sending chunks = receiving retrieve requests 117 // OR sending cheques. 118 // n < 0 called when receiving chunks = receiving delivery responses 119 // OR receiving cheques. 120 121 func NewSwap(local *SwapParams, remote *SwapProfile, backend chequebook.Backend, proto swap.Protocol) (self *swap.Swap, err error) { 122 var ( 123 ctx = context.TODO() 124 ok bool 125 in *chequebook.Inbox 126 out *chequebook.Outbox 127 ) 128 129 // check if remote chequebook is valid 130 // insolvent chequebooks suicide so will signal as invalid 131 // TODO: monitoring a chequebooks events 132 ok, err = chequebook.ValidateCode(ctx, backend, remote.Contract) 133 if !ok { 134 log.Info(fmt.Sprintf("invalid contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err)) 135 } else { 136 // remote contract valid, create inbox 137 in, err = chequebook.NewInbox(local.privateKey, remote.Contract, local.Beneficiary, crypto.ToECDSAPub(common.FromHex(remote.PublicKey)), backend) 138 if err != nil { 139 log.Warn(fmt.Sprintf("unable to set up inbox for chequebook contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err)) 140 } 141 } 142 143 // check if local chequebook contract is valid 144 ok, err = chequebook.ValidateCode(ctx, backend, local.Contract) 145 if !ok { 146 log.Warn(fmt.Sprintf("unable to set up outbox for peer %v: chequebook contract (owner: %v): %v)", proto, local.owner.Hex(), err)) 147 } else { 148 out = chequebook.NewOutbox(local.Chequebook(), remote.Beneficiary) 149 } 150 151 pm := swap.Payment{ 152 In: in, 153 Out: out, 154 Buys: out != nil, 155 Sells: in != nil, 156 } 157 self, err = swap.New(local.Params, pm, proto) 158 if err != nil { 159 return 160 } 161 // remote profile given (first) in handshake 162 self.SetRemote(remote.Profile) 163 var buy, sell string 164 if self.Buys { 165 buy = "purchase from peer enabled at " + remote.SellAt.String() + " wei/chunk" 166 } else { 167 buy = "purchase from peer disabled" 168 } 169 if self.Sells { 170 sell = "selling to peer enabled at " + local.SellAt.String() + " wei/chunk" 171 } else { 172 sell = "selling to peer disabled" 173 } 174 log.Warn(fmt.Sprintf("SWAP arrangement with <%v>: %v; %v)", proto, buy, sell)) 175 176 return 177 } 178 179 func (self *SwapParams) Chequebook() *chequebook.Chequebook { 180 defer self.lock.Unlock() 181 self.lock.Lock() 182 return self.chbook 183 } 184 185 func (self *SwapParams) PrivateKey() *ecdsa.PrivateKey { 186 return self.privateKey 187 } 188 189 // func (self *SwapParams) PublicKey() *ecdsa.PublicKey { 190 // return self.publicKey 191 // } 192 193 func (self *SwapParams) SetKey(prvkey *ecdsa.PrivateKey) { 194 self.privateKey = prvkey 195 self.publicKey = &prvkey.PublicKey 196 } 197 198 // setChequebook(path, backend) wraps the 199 // chequebook initialiser and sets up autoDeposit to cover spending. 200 func (self *SwapParams) SetChequebook(ctx context.Context, backend chequebook.Backend, path string) error { 201 self.lock.Lock() 202 contract := self.Contract 203 self.lock.Unlock() 204 205 valid, err := chequebook.ValidateCode(ctx, backend, contract) 206 if err != nil { 207 return err 208 } else if valid { 209 return self.newChequebookFromContract(path, backend) 210 } 211 return self.deployChequebook(ctx, backend, path) 212 } 213 214 func (self *SwapParams) deployChequebook(ctx context.Context, backend chequebook.Backend, path string) error { 215 opts := bind.NewKeyedTransactor(self.privateKey) 216 opts.Value = self.AutoDepositBuffer 217 opts.Context = ctx 218 219 log.Info(fmt.Sprintf("Deploying new chequebook (owner: %v)", opts.From.Hex())) 220 contract, err := deployChequebookLoop(opts, backend) 221 if err != nil { 222 log.Error(fmt.Sprintf("unable to deploy new chequebook: %v", err)) 223 return err 224 } 225 log.Info(fmt.Sprintf("new chequebook deployed at %v (owner: %v)", contract.Hex(), opts.From.Hex())) 226 227 // need to save config at this point 228 self.lock.Lock() 229 self.Contract = contract 230 err = self.newChequebookFromContract(path, backend) 231 self.lock.Unlock() 232 if err != nil { 233 log.Warn(fmt.Sprintf("error initialising cheque book (owner: %v): %v", opts.From.Hex(), err)) 234 } 235 return err 236 } 237 238 // repeatedly tries to deploy a chequebook. 239 func deployChequebookLoop(opts *bind.TransactOpts, backend chequebook.Backend) (addr common.Address, err error) { 240 var tx *types.Transaction 241 for try := 0; try < chequebookDeployRetries; try++ { 242 if try > 0 { 243 time.Sleep(chequebookDeployDelay) 244 } 245 if _, tx, _, err = contract.DeployChequebook(opts, backend); err != nil { 246 log.Warn(fmt.Sprintf("can't send chequebook deploy tx (try %d): %v", try, err)) 247 continue 248 } 249 if addr, err = bind.WaitDeployed(opts.Context, backend, tx); err != nil { 250 log.Warn(fmt.Sprintf("chequebook deploy error (try %d): %v", try, err)) 251 continue 252 } 253 return addr, nil 254 } 255 return addr, err 256 } 257 258 // initialise the chequebook from a persisted json file or create a new one 259 // caller holds the lock 260 func (self *SwapParams) newChequebookFromContract(path string, backend chequebook.Backend) error { 261 hexkey := common.Bytes2Hex(self.Contract.Bytes()) 262 err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm) 263 if err != nil { 264 return fmt.Errorf("unable to create directory for chequebooks: %v", err) 265 } 266 267 chbookpath := filepath.Join(path, "chequebooks", hexkey+".json") 268 self.chbook, err = chequebook.LoadChequebook(chbookpath, self.privateKey, backend, true) 269 270 if err != nil { 271 self.chbook, err = chequebook.NewChequebook(chbookpath, self.Contract, self.privateKey, backend) 272 if err != nil { 273 log.Warn(fmt.Sprintf("unable to initialise chequebook (owner: %v): %v", self.owner.Hex(), err)) 274 return fmt.Errorf("unable to initialise chequebook (owner: %v): %v", self.owner.Hex(), err) 275 } 276 } 277 278 self.chbook.AutoDeposit(self.AutoDepositInterval, self.AutoDepositThreshold, self.AutoDepositBuffer) 279 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)) 280 281 return nil 282 }