github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/swap/swapprotocol/swapprotocol.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package swapprotocol 6 7 import ( 8 "context" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "math/big" 13 "time" 14 15 "github.com/ethereum/go-ethereum/common" 16 "github.com/ethersphere/bee/v2/pkg/log" 17 "github.com/ethersphere/bee/v2/pkg/p2p" 18 "github.com/ethersphere/bee/v2/pkg/p2p/protobuf" 19 "github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook" 20 swap "github.com/ethersphere/bee/v2/pkg/settlement/swap/headers" 21 "github.com/ethersphere/bee/v2/pkg/settlement/swap/priceoracle" 22 "github.com/ethersphere/bee/v2/pkg/settlement/swap/swapprotocol/pb" 23 "github.com/ethersphere/bee/v2/pkg/swarm" 24 ) 25 26 // loggerName is the tree path name of the logger for this package. 27 const loggerName = "swapprotocol" 28 29 const ( 30 protocolName = "swap" 31 protocolVersion = "1.0.0" 32 streamName = "swap" // stream for cheques 33 ) 34 35 var ( 36 ErrNegotiateRate = errors.New("exchange rates mismatch") 37 ErrNegotiateDeduction = errors.New("deduction values mismatch") 38 ErrHaveDeduction = errors.New("received deduction not zero") 39 ) 40 41 type SendChequeFunc chequebook.SendChequeFunc 42 43 type IssueFunc func(ctx context.Context, beneficiary common.Address, amount *big.Int, sendChequeFunc chequebook.SendChequeFunc) (*big.Int, error) 44 45 // (context.Context, common.Address, *big.Int, chequebook.SendChequeFunc) (*big.Int, error) 46 47 // Interface is the main interface to send messages over swap protocol. 48 type Interface interface { 49 // EmitCheque sends a signed cheque to a peer. 50 EmitCheque(ctx context.Context, peer swarm.Address, beneficiary common.Address, amount *big.Int, issue IssueFunc) (balance *big.Int, err error) 51 } 52 53 // Swap is the interface the settlement layer should implement to receive cheques. 54 type Swap interface { 55 // ReceiveCheque is called by the swap protocol if a cheque is received. 56 ReceiveCheque(ctx context.Context, peer swarm.Address, cheque *chequebook.SignedCheque, exchangeRate, deduction *big.Int) error 57 // Handshake is called by the swap protocol when a handshake is received. 58 Handshake(peer swarm.Address, beneficiary common.Address) error 59 GetDeductionForPeer(peer swarm.Address) (bool, error) 60 GetDeductionByPeer(peer swarm.Address) (bool, error) 61 AddDeductionByPeer(peer swarm.Address) error 62 } 63 64 // Service is the main implementation of the swap protocol. 65 type Service struct { 66 streamer p2p.Streamer 67 logger log.Logger 68 swap Swap 69 priceOracle priceoracle.Service 70 beneficiary common.Address 71 } 72 73 // New creates a new swap protocol Service. 74 func New(streamer p2p.Streamer, logger log.Logger, beneficiary common.Address, priceOracle priceoracle.Service) *Service { 75 return &Service{ 76 streamer: streamer, 77 logger: logger.WithName(loggerName).Register(), 78 beneficiary: beneficiary, 79 priceOracle: priceOracle, 80 } 81 } 82 83 // SetSwap sets the swap to notify. 84 func (s *Service) SetSwap(swap Swap) { 85 s.swap = swap 86 } 87 88 func (s *Service) Protocol() p2p.ProtocolSpec { 89 return p2p.ProtocolSpec{ 90 Name: protocolName, 91 Version: protocolVersion, 92 StreamSpecs: []p2p.StreamSpec{ 93 { 94 Name: streamName, 95 Handler: s.handler, 96 Headler: s.headler, 97 }, 98 }, 99 ConnectOut: s.init, 100 ConnectIn: s.init, 101 } 102 } 103 104 // init is called on outgoing connections and triggers handshake exchange 105 func (s *Service) init(ctx context.Context, p p2p.Peer) error { 106 beneficiary := common.BytesToAddress(p.EthereumAddress) 107 return s.swap.Handshake(p.Address, beneficiary) 108 } 109 110 func (s *Service) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) (err error) { 111 r := protobuf.NewReader(stream) 112 defer func() { 113 if err != nil { 114 _ = stream.Reset() 115 } else { 116 _ = stream.FullClose() 117 } 118 }() 119 120 var req pb.EmitCheque 121 if err := r.ReadMsgWithContext(ctx, &req); err != nil { 122 return fmt.Errorf("read request from peer %v: %w", p.Address, err) 123 } 124 125 responseHeaders := stream.ResponseHeaders() 126 exchangeRate, deduction, err := swap.ParseSettlementResponseHeaders(responseHeaders) 127 if err != nil { 128 if !errors.Is(err, swap.ErrNoDeductionHeader) { 129 return err 130 } 131 deduction = big.NewInt(0) 132 } 133 134 var signedCheque *chequebook.SignedCheque 135 err = json.Unmarshal(req.Cheque, &signedCheque) 136 if err != nil { 137 return err 138 } 139 140 // signature validation 141 return s.swap.ReceiveCheque(ctx, p.Address, signedCheque, exchangeRate, deduction) 142 } 143 144 func (s *Service) headler(receivedHeaders p2p.Headers, peerAddress swarm.Address) (returnHeaders p2p.Headers) { 145 146 exchangeRate, deduction, err := s.priceOracle.CurrentRates() 147 if err != nil { 148 return p2p.Headers{} 149 } 150 151 checkPeer, err := s.swap.GetDeductionForPeer(peerAddress) 152 if err != nil { 153 return p2p.Headers{} 154 } 155 156 if checkPeer { 157 deduction = big.NewInt(0) 158 } 159 160 returnHeaders = swap.MakeSettlementHeaders(exchangeRate, deduction) 161 return 162 } 163 164 // InitiateCheque attempts to send a cheque to a peer. 165 func (s *Service) EmitCheque(ctx context.Context, peer swarm.Address, beneficiary common.Address, amount *big.Int, issue IssueFunc) (balance *big.Int, err error) { 166 loggerV1 := s.logger.V(1).Register() 167 168 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 169 defer cancel() 170 171 stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, streamName) 172 if err != nil { 173 return nil, err 174 } 175 defer func() { 176 if err != nil { 177 _ = stream.Reset() 178 } else { 179 _ = stream.FullClose() 180 } 181 }() 182 183 // reading exchangeRated headers 184 returnedHeaders := stream.Headers() 185 exchangeRate, deduction, err := swap.ParseSettlementResponseHeaders(returnedHeaders) 186 if err != nil { 187 if !errors.Is(err, swap.ErrNoDeductionHeader) { 188 return nil, err 189 } 190 deduction = big.NewInt(0) 191 } 192 193 // comparing received headers to known truth 194 195 // get whether peer have deducted in the past 196 checkPeer, err := s.swap.GetDeductionByPeer(peer) 197 if err != nil { 198 return nil, err 199 } 200 201 // if peer is not entitled for deduction but sent non zero deduction value, return with error 202 if checkPeer && deduction.Cmp(big.NewInt(0)) != 0 { 203 return nil, ErrHaveDeduction 204 } 205 206 // get current global exchangeRate rate and deduction 207 checkExchangeRate, checkDeduction, err := s.priceOracle.CurrentRates() 208 if err != nil { 209 return nil, err 210 } 211 212 // exchangeRate rates should match 213 if exchangeRate.Cmp(checkExchangeRate) != 0 { 214 return nil, ErrNegotiateRate 215 } 216 217 // deduction values should match or be zero 218 if deduction.Cmp(checkDeduction) != 0 && deduction.Cmp(big.NewInt(0)) != 0 { 219 return nil, ErrNegotiateDeduction 220 } 221 222 paymentAmount := new(big.Int).Mul(amount, exchangeRate) 223 sentAmount := new(big.Int).Add(paymentAmount, deduction) 224 225 // issue cheque call with provided callback for sending cheque to finish transaction 226 227 balance, err = issue(ctx, beneficiary, sentAmount, func(cheque *chequebook.SignedCheque) error { 228 // for simplicity we use json marshaller. can be replaced by a binary encoding in the future. 229 encodedCheque, err := json.Marshal(cheque) 230 if err != nil { 231 return err 232 } 233 234 // sending cheque 235 loggerV1.Debug("sending cheque message to peer", "peer_address", peer, "cheque", cheque) 236 237 w := protobuf.NewWriter(stream) 238 return w.WriteMsgWithContext(ctx, &pb.EmitCheque{ 239 Cheque: encodedCheque, 240 }) 241 242 }) 243 if err != nil { 244 return nil, err 245 } 246 247 if deduction.Cmp(big.NewInt(0)) != 0 { 248 err = s.swap.AddDeductionByPeer(peer) 249 if err != nil { 250 return nil, err 251 } 252 } 253 254 return balance, nil 255 }