github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/contracts/chequebook/cheque.go (about) 1 // Package chequebook package wraps the 'chequebook' Ethereum smart contract. 2 // The functions in this package allow using chequebook for 3 // issuing, receiving, verifying cheques in ether; (auto)cashing cheques in ether 4 // as well as (auto)depositing ether to the chequebook contract. 5 package chequebook 6 7 //go:generate abigen --sol contract/chequebook.sol --exc contract/mortal.sol:mortal,contract/owned.sol:owned --pkg contract --out contract/chequebook.go 8 //go:generate go run ./gencode.go 9 10 import ( 11 "bytes" 12 "context" 13 "crypto/ecdsa" 14 "encoding/json" 15 "fmt" 16 "io/ioutil" 17 "math/big" 18 "os" 19 "sync" 20 "time" 21 22 "github.com/quickchainproject/quickchain/accounts/abi/bind" 23 "github.com/quickchainproject/quickchain/common" 24 "github.com/quickchainproject/quickchain/common/hexutil" 25 "github.com/quickchainproject/quickchain/contracts/chequebook/contract" 26 "github.com/quickchainproject/quickchain/core/types" 27 "github.com/quickchainproject/quickchain/crypto" 28 "github.com/quickchainproject/quickchain/log" 29 ) 30 31 // TODO(zelig): watch peer solvency and notify of bouncing cheques 32 // TODO(zelig): enable paying with cheque by signing off 33 34 // Some functionality requires interacting with the blockchain: 35 // * setting current balance on peer's chequebook 36 // * sending the transaction to cash the cheque 37 // * depositing ether to the chequebook 38 // * watching incoming ether 39 40 var ( 41 gasToCash = uint64(2000000) // gas cost of a cash transaction using chequebook 42 // gasToDeploy = uint64(3000000) 43 ) 44 45 // Backend wraps all methods required for chequebook operation. 46 type Backend interface { 47 bind.ContractBackend 48 TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) 49 BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error) 50 } 51 52 // Cheque represents a payment promise to a single beneficiary. 53 type Cheque struct { 54 Contract common.Address // address of chequebook, needed to avoid cross-contract submission 55 Beneficiary common.Address 56 Amount *big.Int // cumulative amount of all funds sent 57 Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey) 58 } 59 60 func (self *Cheque) String() string { 61 return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig) 62 } 63 64 type Params struct { 65 ContractCode, ContractAbi string 66 } 67 68 var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI} 69 70 // Promise 71 // 3rd party Provable Promise of Payment 72 // issued by outPayment 73 // serialisable to send with Protocol 74 type Promise interface{} 75 76 // Chequebook can create and sign cheques from a single contract to multiple beneficiaries. 77 // It is the outgoing payment handler for peer to peer micropayments. 78 type Chequebook struct { 79 path string // path to chequebook file 80 prvKey *ecdsa.PrivateKey // private key to sign cheque with 81 lock sync.Mutex // 82 backend Backend // blockchain API 83 quit chan bool // when closed causes autodeposit to stop 84 owner common.Address // owner address (derived from pubkey) 85 contract *contract.Chequebook // abigen binding 86 session *contract.ChequebookSession // abigen binding with Tx Opts 87 88 // persisted fields 89 balance *big.Int // not synced with blockchain 90 contractAddr common.Address // contract address 91 sent map[common.Address]*big.Int //tallies for beneficiaries 92 93 txhash string // tx hash of last deposit tx 94 threshold *big.Int // threshold that triggers autodeposit if not nil 95 buffer *big.Int // buffer to keep on top of balance for fork protection 96 97 log log.Logger // contextual logger with the contract address embedded 98 } 99 100 func (self *Chequebook) String() string { 101 return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey) 102 } 103 104 // NewChequebook creates a new Chequebook. 105 func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { 106 balance := new(big.Int) 107 sent := make(map[common.Address]*big.Int) 108 109 chbook, err := contract.NewChequebook(contractAddr, backend) 110 if err != nil { 111 return nil, err 112 } 113 transactOpts := bind.NewKeyedTransactor(prvKey) 114 session := &contract.ChequebookSession{ 115 Contract: chbook, 116 TransactOpts: *transactOpts, 117 } 118 119 self = &Chequebook{ 120 prvKey: prvKey, 121 balance: balance, 122 contractAddr: contractAddr, 123 sent: sent, 124 path: path, 125 backend: backend, 126 owner: transactOpts.From, 127 contract: chbook, 128 session: session, 129 log: log.New("contract", contractAddr), 130 } 131 132 if (contractAddr != common.Address{}) { 133 self.setBalanceFromBlockChain() 134 self.log.Trace("New chequebook initialised", "owner", self.owner, "balance", self.balance) 135 } 136 return 137 } 138 139 func (self *Chequebook) setBalanceFromBlockChain() { 140 balance, err := self.backend.BalanceAt(context.TODO(), self.contractAddr, nil) 141 if err != nil { 142 log.Error("Failed to retrieve chequebook balance", "err", err) 143 } else { 144 self.balance.Set(balance) 145 } 146 } 147 148 // LoadChequebook loads a chequebook from disk (file path). 149 func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (self *Chequebook, err error) { 150 var data []byte 151 data, err = ioutil.ReadFile(path) 152 if err != nil { 153 return 154 } 155 self, _ = NewChequebook(path, common.Address{}, prvKey, backend) 156 157 err = json.Unmarshal(data, self) 158 if err != nil { 159 return nil, err 160 } 161 if checkBalance { 162 self.setBalanceFromBlockChain() 163 } 164 log.Trace("Loaded chequebook from disk", "path", path) 165 166 return 167 } 168 169 // chequebookFile is the JSON representation of a chequebook. 170 type chequebookFile struct { 171 Balance string 172 Contract string 173 Owner string 174 Sent map[string]string 175 } 176 177 // UnmarshalJSON deserialises a chequebook. 178 func (self *Chequebook) UnmarshalJSON(data []byte) error { 179 var file chequebookFile 180 err := json.Unmarshal(data, &file) 181 if err != nil { 182 return err 183 } 184 _, ok := self.balance.SetString(file.Balance, 10) 185 if !ok { 186 return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) 187 } 188 self.contractAddr = common.HexToAddress(file.Contract) 189 for addr, sent := range file.Sent { 190 self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) 191 if !ok { 192 return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) 193 } 194 } 195 return nil 196 } 197 198 // MarshalJSON serialises a chequebook. 199 func (self *Chequebook) MarshalJSON() ([]byte, error) { 200 var file = &chequebookFile{ 201 Balance: self.balance.String(), 202 Contract: self.contractAddr.Hex(), 203 Owner: self.owner.Hex(), 204 Sent: make(map[string]string), 205 } 206 for addr, sent := range self.sent { 207 file.Sent[addr.Hex()] = sent.String() 208 } 209 return json.Marshal(file) 210 } 211 212 // Save persists the chequebook on disk, remembering balance, contract address and 213 // cumulative amount of funds sent for each beneficiary. 214 func (self *Chequebook) Save() (err error) { 215 data, err := json.MarshalIndent(self, "", " ") 216 if err != nil { 217 return err 218 } 219 self.log.Trace("Saving chequebook to disk", self.path) 220 221 return ioutil.WriteFile(self.path, data, os.ModePerm) 222 } 223 224 // Stop quits the autodeposit go routine to terminate 225 func (self *Chequebook) Stop() { 226 defer self.lock.Unlock() 227 self.lock.Lock() 228 if self.quit != nil { 229 close(self.quit) 230 self.quit = nil 231 } 232 } 233 234 // Issue creates a cheque signed by the chequebook owner's private key. The 235 // signer commits to a contract (one that they own), a beneficiary and amount. 236 func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { 237 defer self.lock.Unlock() 238 self.lock.Lock() 239 240 if amount.Sign() <= 0 { 241 return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) 242 } 243 if self.balance.Cmp(amount) < 0 { 244 err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, self.balance) 245 } else { 246 var sig []byte 247 sent, found := self.sent[beneficiary] 248 if !found { 249 sent = new(big.Int) 250 self.sent[beneficiary] = sent 251 } 252 sum := new(big.Int).Set(sent) 253 sum.Add(sum, amount) 254 255 sig, err = crypto.Sign(sigHash(self.contractAddr, beneficiary, sum), self.prvKey) 256 if err == nil { 257 ch = &Cheque{ 258 Contract: self.contractAddr, 259 Beneficiary: beneficiary, 260 Amount: sum, 261 Sig: sig, 262 } 263 sent.Set(sum) 264 self.balance.Sub(self.balance, amount) // subtract amount from balance 265 } 266 } 267 268 // auto deposit if threshold is set and balance is less then threshold 269 // note this is called even if issuing cheque fails 270 // so we reattempt depositing 271 if self.threshold != nil { 272 if self.balance.Cmp(self.threshold) < 0 { 273 send := new(big.Int).Sub(self.buffer, self.balance) 274 self.deposit(send) 275 } 276 } 277 278 return 279 } 280 281 // Cash is a convenience method to cash any cheque. 282 func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { 283 return ch.Cash(self.session) 284 } 285 286 // data to sign: contract address, beneficiary, cumulative amount of funds ever sent 287 func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { 288 bigamount := sum.Bytes() 289 if len(bigamount) > 32 { 290 return nil 291 } 292 var amount32 [32]byte 293 copy(amount32[32-len(bigamount):32], bigamount) 294 input := append(contract.Bytes(), beneficiary.Bytes()...) 295 input = append(input, amount32[:]...) 296 return crypto.Keccak256(input) 297 } 298 299 // Balance returns the current balance of the chequebook. 300 func (self *Chequebook) Balance() *big.Int { 301 defer self.lock.Unlock() 302 self.lock.Lock() 303 return new(big.Int).Set(self.balance) 304 } 305 306 // Owner returns the owner account of the chequebook. 307 func (self *Chequebook) Owner() common.Address { 308 return self.owner 309 } 310 311 // Address returns the on-chain contract address of the chequebook. 312 func (self *Chequebook) Address() common.Address { 313 return self.contractAddr 314 } 315 316 // Deposit deposits money to the chequebook account. 317 func (self *Chequebook) Deposit(amount *big.Int) (string, error) { 318 defer self.lock.Unlock() 319 self.lock.Lock() 320 return self.deposit(amount) 321 } 322 323 // deposit deposits amount to the chequebook account. 324 // The caller must hold self.lock. 325 func (self *Chequebook) deposit(amount *big.Int) (string, error) { 326 // since the amount is variable here, we do not use sessions 327 depositTransactor := bind.NewKeyedTransactor(self.prvKey) 328 depositTransactor.Value = amount 329 chbookRaw := &contract.ChequebookRaw{Contract: self.contract} 330 tx, err := chbookRaw.Transfer(depositTransactor) 331 if err != nil { 332 self.log.Warn("Failed to fund chequebook", "amount", amount, "balance", self.balance, "target", self.buffer, "err", err) 333 return "", err 334 } 335 // assume that transaction is actually successful, we add the amount to balance right away 336 self.balance.Add(self.balance, amount) 337 self.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", self.balance, "target", self.buffer) 338 return tx.Hash().Hex(), nil 339 } 340 341 // AutoDeposit (re)sets interval time and amount which triggers sending funds to the 342 // chequebook. Contract backend needs to be set if threshold is not less than buffer, then 343 // deposit will be triggered on every new cheque issued. 344 func (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 345 defer self.lock.Unlock() 346 self.lock.Lock() 347 self.threshold = threshold 348 self.buffer = buffer 349 self.autoDeposit(interval) 350 } 351 352 // autoDeposit starts a goroutine that periodically sends funds to the chequebook 353 // contract caller holds the lock the go routine terminates if Chequebook.quit is closed. 354 func (self *Chequebook) autoDeposit(interval time.Duration) { 355 if self.quit != nil { 356 close(self.quit) 357 self.quit = nil 358 } 359 // if threshold >= balance autodeposit after every cheque issued 360 if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 { 361 return 362 } 363 364 ticker := time.NewTicker(interval) 365 self.quit = make(chan bool) 366 quit := self.quit 367 368 go func() { 369 for { 370 select { 371 case <-quit: 372 return 373 case <-ticker.C: 374 self.lock.Lock() 375 if self.balance.Cmp(self.buffer) < 0 { 376 amount := new(big.Int).Sub(self.buffer, self.balance) 377 txhash, err := self.deposit(amount) 378 if err == nil { 379 self.txhash = txhash 380 } 381 } 382 self.lock.Unlock() 383 } 384 } 385 }() 386 } 387 388 // Outbox can issue cheques from a single contract to a single beneficiary. 389 type Outbox struct { 390 chequeBook *Chequebook 391 beneficiary common.Address 392 } 393 394 // NewOutbox creates an outbox. 395 func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox { 396 return &Outbox{chbook, beneficiary} 397 } 398 399 // Issue creates cheque. 400 func (self *Outbox) Issue(amount *big.Int) (Promise, error) { 401 return self.chequeBook.Issue(self.beneficiary, amount) 402 } 403 404 // AutoDeposit enables auto-deposits on the underlying chequebook. 405 func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 406 self.chequeBook.AutoDeposit(interval, threshold, buffer) 407 } 408 409 // Stop helps satisfy the swap.OutPayment interface. 410 func (self *Outbox) Stop() {} 411 412 // String implements fmt.Stringer. 413 func (self *Outbox) String() string { 414 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance()) 415 } 416 417 // Inbox can deposit, verify and cash cheques from a single contract to a single 418 // beneficiary. It is the incoming payment handler for peer to peer micropayments. 419 type Inbox struct { 420 lock sync.Mutex 421 contract common.Address // peer's chequebook contract 422 beneficiary common.Address // local peer's receiving address 423 sender common.Address // local peer's address to send cashing tx from 424 signer *ecdsa.PublicKey // peer's public key 425 txhash string // tx hash of last cashing tx 426 session *contract.ChequebookSession // abi contract backend with tx opts 427 quit chan bool // when closed causes autocash to stop 428 maxUncashed *big.Int // threshold that triggers autocashing 429 cashed *big.Int // cumulative amount cashed 430 cheque *Cheque // last cheque, nil if none yet received 431 log log.Logger // contextual logger with the contract address embedded 432 } 433 434 // NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated 435 // from blockchain when first cheque is received. 436 func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (self *Inbox, err error) { 437 if signer == nil { 438 return nil, fmt.Errorf("signer is null") 439 } 440 chbook, err := contract.NewChequebook(contractAddr, abigen) 441 if err != nil { 442 return nil, err 443 } 444 transactOpts := bind.NewKeyedTransactor(prvKey) 445 transactOpts.GasLimit = gasToCash 446 session := &contract.ChequebookSession{ 447 Contract: chbook, 448 TransactOpts: *transactOpts, 449 } 450 sender := transactOpts.From 451 452 self = &Inbox{ 453 contract: contractAddr, 454 beneficiary: beneficiary, 455 sender: sender, 456 signer: signer, 457 session: session, 458 cashed: new(big.Int).Set(common.Big0), 459 log: log.New("contract", contractAddr), 460 } 461 self.log.Trace("New chequebook inbox initialized", "beneficiary", self.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer))) 462 return 463 } 464 465 func (self *Inbox) String() string { 466 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount) 467 } 468 469 // Stop quits the autocash goroutine. 470 func (self *Inbox) Stop() { 471 defer self.lock.Unlock() 472 self.lock.Lock() 473 if self.quit != nil { 474 close(self.quit) 475 self.quit = nil 476 } 477 } 478 479 // Cash attempts to cash the current cheque. 480 func (self *Inbox) Cash() (txhash string, err error) { 481 if self.cheque != nil { 482 txhash, err = self.cheque.Cash(self.session) 483 self.log.Trace("Cashing in chequebook cheque", "amount", self.cheque.Amount, "beneficiary", self.beneficiary) 484 self.cashed = self.cheque.Amount 485 } 486 return 487 } 488 489 // AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed 490 // cheque if maxUncashed is set to 0, then autocash on receipt. 491 func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { 492 defer self.lock.Unlock() 493 self.lock.Lock() 494 self.maxUncashed = maxUncashed 495 self.autoCash(cashInterval) 496 } 497 498 // autoCash starts a loop that periodically clears the last cheque 499 // if the peer is trusted. Clearing period could be 24h or a week. 500 // The caller must hold self.lock. 501 func (self *Inbox) autoCash(cashInterval time.Duration) { 502 if self.quit != nil { 503 close(self.quit) 504 self.quit = nil 505 } 506 // if maxUncashed is set to 0, then autocash on receipt 507 if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Sign() == 0 { 508 return 509 } 510 511 ticker := time.NewTicker(cashInterval) 512 self.quit = make(chan bool) 513 quit := self.quit 514 515 go func() { 516 for { 517 select { 518 case <-quit: 519 return 520 case <-ticker.C: 521 self.lock.Lock() 522 if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 { 523 txhash, err := self.Cash() 524 if err == nil { 525 self.txhash = txhash 526 } 527 } 528 self.lock.Unlock() 529 } 530 } 531 }() 532 } 533 534 // Receive is called to deposit the latest cheque to the incoming Inbox. 535 // The given promise must be a *Cheque. 536 func (self *Inbox) Receive(promise Promise) (*big.Int, error) { 537 ch := promise.(*Cheque) 538 539 defer self.lock.Unlock() 540 self.lock.Lock() 541 542 var sum *big.Int 543 if self.cheque == nil { 544 // the sum is checked against the blockchain once a cheque is received 545 tally, err := self.session.Sent(self.beneficiary) 546 if err != nil { 547 return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) 548 } 549 sum = tally 550 } else { 551 sum = self.cheque.Amount 552 } 553 554 amount, err := ch.Verify(self.signer, self.contract, self.beneficiary, sum) 555 var uncashed *big.Int 556 if err == nil { 557 self.cheque = ch 558 559 if self.maxUncashed != nil { 560 uncashed = new(big.Int).Sub(ch.Amount, self.cashed) 561 if self.maxUncashed.Cmp(uncashed) < 0 { 562 self.Cash() 563 } 564 } 565 self.log.Trace("Received cheque in chequebook inbox", "amount", amount, "uncashed", uncashed) 566 } 567 568 return amount, err 569 } 570 571 // Verify verifies cheque for signer, contract, beneficiary, amount, valid signature. 572 func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { 573 log.Trace("Verifying chequebook cheque", "cheque", self, "sum", sum) 574 if sum == nil { 575 return nil, fmt.Errorf("invalid amount") 576 } 577 578 if self.Beneficiary != beneficiary { 579 return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex()) 580 } 581 if self.Contract != contract { 582 return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex()) 583 } 584 585 amount := new(big.Int).Set(self.Amount) 586 if sum != nil { 587 amount.Sub(amount, sum) 588 if amount.Sign() <= 0 { 589 return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) 590 } 591 } 592 593 pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.Sig) 594 if err != nil { 595 return nil, fmt.Errorf("invalid signature: %v", err) 596 } 597 if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { 598 return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) 599 } 600 return amount, nil 601 } 602 603 // v/r/s representation of signature 604 func sig2vrs(sig []byte) (v byte, r, s [32]byte) { 605 v = sig[64] + 27 606 copy(r[:], sig[:32]) 607 copy(s[:], sig[32:64]) 608 return 609 } 610 611 // Cash cashes the cheque by sending an Ethereum transaction. 612 func (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) { 613 v, r, s := sig2vrs(self.Sig) 614 tx, err := session.Cash(self.Beneficiary, self.Amount, v, r, s) 615 if err != nil { 616 return "", err 617 } 618 return tx.Hash().Hex(), nil 619 } 620 621 // ValidateCode checks that the on-chain code at address matches the expected chequebook 622 // contract code. This is used to detect suicided chequebooks. 623 func ValidateCode(ctx context.Context, b Backend, address common.Address) (ok bool, err error) { 624 code, err := b.CodeAt(ctx, address, nil) 625 if err != nil { 626 return false, err 627 } 628 return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil 629 }