swap.go 9.8 KB

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