mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 14:16:44 +00:00
660 lines
21 KiB
Go
660 lines
21 KiB
Go
package chequebook
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/swap"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
"github.com/ethereum/go-ethereum/logger/glog"
|
|
)
|
|
|
|
/*
|
|
Chequebook package is a go API to the 'chequebook' ethereum smart contract
|
|
With convenience methods that allow using chequebook for
|
|
* issuing, receiving, verifying cheques in ether
|
|
* (auto)cashing cheques in ether
|
|
* (auto)depositing ether to the chequebook contract
|
|
TODO:
|
|
* watch peer solvency and notify of bouncing cheques
|
|
* enable paying with cheque by signing off
|
|
|
|
Some functionality require interacting with the blockchain:
|
|
* setting current balance on peer's chequebook
|
|
* sending the transaction to cash the cheque
|
|
* depositing ether to the chequebook
|
|
* watching incoming ether
|
|
|
|
Backend is the interface for that
|
|
*/
|
|
|
|
const (
|
|
gasToCash = "2000000" // gas cost of a cash transaction using chequebook
|
|
getSentAbiPre = "d75d691d" // sent amount accessor in the chequebook contract
|
|
cashAbiPre = "fbf788d6" // abi preamble signature for cash method of the chequebook
|
|
queryInterval = 15000000000 // 15 seconds
|
|
deployGas = "3000000"
|
|
// confirmationInterval = 3 * 10 * *11 // 5 minutes
|
|
)
|
|
|
|
// Backend is the interface to interact with the Ethereum blockchain
|
|
// implemented by xeth.XEth
|
|
type Backend interface {
|
|
Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error)
|
|
Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error)
|
|
GetTxReceipt(txhash common.Hash) *types.Receipt
|
|
CodeAt(address string) string
|
|
}
|
|
|
|
// rlp serialised cheque model for use with the chequebook
|
|
type Cheque struct {
|
|
// the address of the contract itself needed to avoid cross-contract submission
|
|
Contract common.Address // contract address
|
|
Beneficiary common.Address // beneficiary
|
|
Amount *big.Int // cumulative amount of all funds sent
|
|
Sig []byte // signature Sign(Sha3(contract, beneficiary, amount), prvKey)
|
|
}
|
|
|
|
type Params struct {
|
|
ContractCode, ContractAbi, ContractSource string
|
|
}
|
|
|
|
var ContractParams = &Params{ContractCode, ContractAbi, ContractSource}
|
|
|
|
func (self *Cheque) String() string {
|
|
return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig)
|
|
}
|
|
|
|
// chequebook to create, sign cheques from single contract to multiple beneficiarys
|
|
// outgoing payment handler for peer to peer micropayments
|
|
type Chequebook struct {
|
|
path string // path to chequebook file
|
|
prvKey *ecdsa.PrivateKey // private key to sign cheque with
|
|
lock sync.Mutex //
|
|
backend Backend // blockchain API
|
|
quit chan bool // when closed causes autodeposit to stop
|
|
owner common.Address // owner address (derived from pubkey)
|
|
|
|
// persisted fields
|
|
balance *big.Int // not synced with blockchain
|
|
contract common.Address // contract address
|
|
sent map[common.Address]*big.Int //tallies for beneficiarys
|
|
|
|
txhash string // tx hash of last deposit tx
|
|
threshold *big.Int // threshold that triggers autodeposit if not nil
|
|
buffer *big.Int // buffer to keep on top of balance for fork protection
|
|
}
|
|
|
|
func Deploy(owner common.Address, backend Backend, amount *big.Int, confirmationInterval, timeout time.Duration) (contract common.Address, err error) {
|
|
if (owner == common.Address{}) {
|
|
return contract, fmt.Errorf("invalid owner %v. Owner needed to deploy chequebook contract: %v", owner.Hex(), err)
|
|
}
|
|
|
|
txhash, err := backend.Transact(owner.Hex(), "", "", amount.String(), deployGas, "", ContractCode)
|
|
if err != nil {
|
|
return contract, fmt.Errorf("unable to send chequebook creation transaction: %v", err)
|
|
}
|
|
|
|
timer := time.NewTimer(timeout).C
|
|
ticker := time.NewTicker(queryInterval).C
|
|
OUT:
|
|
for {
|
|
select {
|
|
|
|
case <-timer:
|
|
if ticker == nil {
|
|
// ticker is nil, receipt was found and confirmation interval passed
|
|
err = Validate(contract, backend)
|
|
if err != nil {
|
|
return contract, fmt.Errorf("invalid contract at %v after %v: %v", contract.Hex(), confirmationInterval, err)
|
|
}
|
|
break OUT
|
|
}
|
|
// ticker is non-nil meaning receipt not found yet
|
|
return contract, fmt.Errorf("chequebook deployment timed out in %v", timeout)
|
|
|
|
case <-ticker:
|
|
receipt := backend.GetTxReceipt(common.HexToHash(txhash))
|
|
if receipt != nil {
|
|
contract = receipt.ContractAddress
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] chequebook deployed at %v (owner: %v)", contract.Hex(), owner.Hex())
|
|
timer = time.NewTimer(confirmationInterval).C
|
|
ticker = nil
|
|
} else {
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] check if chequebook deployed (txhash: %v)", txhash)
|
|
}
|
|
|
|
}
|
|
}
|
|
return contract, nil
|
|
}
|
|
|
|
func Validate(contract common.Address, backend Backend) (err error) {
|
|
if (contract == common.Address{}) {
|
|
return fmt.Errorf("zero address")
|
|
}
|
|
code := backend.CodeAt(contract.Hex())
|
|
if code != ContractDeployedCode {
|
|
return fmt.Errorf("incorrect code %v:\n%v\n%v", contract.Hex(), code, ContractDeployedCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewChequebook(path, contract, balance, prvKey) creates a new Chequebook
|
|
func NewChequebook(path string, contract common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) {
|
|
balance := new(big.Int)
|
|
sent := make(map[common.Address]*big.Int)
|
|
owner := crypto.PubkeyToAddress(prvKey.PublicKey)
|
|
self = &Chequebook{
|
|
balance: balance,
|
|
contract: contract,
|
|
sent: sent,
|
|
path: path,
|
|
prvKey: prvKey,
|
|
backend: backend,
|
|
owner: owner,
|
|
}
|
|
if (contract != common.Address{}) {
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v)", contract.Hex(), owner.Hex())
|
|
}
|
|
return
|
|
}
|
|
|
|
// LoadChequebook(path, prvKey, backend) loads a chequebook from disk (file path)
|
|
func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) {
|
|
var data []byte
|
|
data, err = ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
self, _ = NewChequebook(path, common.Address{}, prvKey, backend)
|
|
|
|
err = json.Unmarshal(data, self)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] loaded chequebook (%s, owner: %v) initialised from %v", self.contract.Hex(), self.owner.Hex(), path)
|
|
|
|
return
|
|
}
|
|
|
|
// chequebook serialisation
|
|
type chequebookFile struct {
|
|
Balance string
|
|
Contract string
|
|
Owner string
|
|
Sent map[string]string
|
|
}
|
|
|
|
func (self *Chequebook) UnmarshalJSON(data []byte) error {
|
|
var file chequebookFile
|
|
err := json.Unmarshal(data, &file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, ok := self.balance.SetString(file.Balance, 10)
|
|
if !ok {
|
|
return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance)
|
|
}
|
|
self.contract = common.HexToAddress(file.Contract)
|
|
for addr, sent := range file.Sent {
|
|
self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10)
|
|
if !ok {
|
|
return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *Chequebook) MarshalJSON() ([]byte, error) {
|
|
var file = &chequebookFile{
|
|
Balance: self.balance.String(),
|
|
Contract: self.contract.Hex(),
|
|
Owner: self.owner.Hex(),
|
|
Sent: make(map[string]string),
|
|
}
|
|
for addr, sent := range self.sent {
|
|
file.Sent[addr.Hex()] = sent.String()
|
|
}
|
|
return json.Marshal(file)
|
|
}
|
|
|
|
// Save() persists the chequebook on disk
|
|
// remembers balance, contract address and
|
|
// cumulative amount of funds sent for each beneficiary
|
|
func (self *Chequebook) Save() (err error) {
|
|
data, err := json.MarshalIndent(self, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] saving chequebook (%s) to %v", self.contract.Hex(), self.path)
|
|
|
|
return ioutil.WriteFile(self.path, data, os.ModePerm)
|
|
}
|
|
|
|
// Stop() quits the autodeposit go routine to terminate
|
|
func (self *Chequebook) Stop() {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
if self.quit != nil {
|
|
close(self.quit)
|
|
self.quit = nil
|
|
}
|
|
}
|
|
|
|
// Issue(beneficiary, amount) will create a Cheque
|
|
// the cheque is signed by the chequebook owner's private key
|
|
// the signer commits to a contract (one that they own), a beneficiary and amount
|
|
func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
if amount.Cmp(common.Big0) <= 0 {
|
|
return nil, fmt.Errorf("amount must be greater than zero (%v)", amount)
|
|
}
|
|
if self.balance.Cmp(amount) < 0 {
|
|
err = fmt.Errorf("insufficent funds to issue cheque for amount: %v. balance: %v", amount, self.balance)
|
|
} else {
|
|
var sig []byte
|
|
sent, found := self.sent[beneficiary]
|
|
if !found {
|
|
sent = new(big.Int)
|
|
self.sent[beneficiary] = sent
|
|
}
|
|
sum := new(big.Int).Set(sent)
|
|
sum.Add(sum, amount)
|
|
sig, err = crypto.Sign(sigHash(self.contract, beneficiary, sum), self.prvKey)
|
|
if err == nil {
|
|
ch = &Cheque{
|
|
Contract: self.contract,
|
|
Beneficiary: beneficiary,
|
|
Amount: sum,
|
|
Sig: sig,
|
|
}
|
|
sent.Set(sum)
|
|
self.balance.Sub(self.balance, amount) // subtract amount from balance
|
|
}
|
|
}
|
|
|
|
// auto deposit if threshold is set and balance is less then threshold
|
|
// note this is called even if issueing cheque fails
|
|
// so we reattempt depositing
|
|
if self.threshold != nil {
|
|
if self.balance.Cmp(self.threshold) < 0 {
|
|
send := new(big.Int).Sub(self.buffer, self.balance)
|
|
self.deposit(send)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// convenience method to cash any cheque
|
|
func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) {
|
|
return ch.Cash(self.owner, self.backend)
|
|
}
|
|
|
|
// data to sign: contract address, beneficiary, cumulative amount of funds ever sent
|
|
func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte {
|
|
bigamount := sum.Bytes()
|
|
if len(bigamount) > 32 {
|
|
return nil
|
|
}
|
|
var amount32 [32]byte
|
|
copy(amount32[32-len(bigamount):32], bigamount)
|
|
input := append(contract.Bytes(), beneficiary.Bytes()...)
|
|
input = append(input, amount32[:]...)
|
|
return crypto.Sha3(input)
|
|
}
|
|
|
|
// Balance() public accessor for balance
|
|
func (self *Chequebook) Balance() *big.Int {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
return new(big.Int).Set(self.balance)
|
|
}
|
|
|
|
// Balance() public accessor for balance
|
|
func (self *Chequebook) Owner() common.Address {
|
|
return self.owner
|
|
}
|
|
|
|
// Backend() public accessor for backend
|
|
func (self *Chequebook) Backend() Backend {
|
|
return self.backend
|
|
}
|
|
|
|
// Address() public accessor for contract
|
|
func (self *Chequebook) Address() common.Address {
|
|
return self.contract
|
|
}
|
|
|
|
// Deposit(amount) deposits amount to the chequebook account
|
|
func (self *Chequebook) Deposit(amount *big.Int) (string, error) {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
return self.deposit(amount)
|
|
}
|
|
|
|
// deposit(amount) deposits amount to the chequebook account
|
|
// caller holds the lock
|
|
func (self *Chequebook) deposit(amount *big.Int) (string, error) {
|
|
txhash, err := self.backend.Transact(self.owner.Hex(), self.contract.Hex(), "", amount.String(), "", "", "")
|
|
// assume that transaction is actually successful, we add the amount to balance right away
|
|
if err != nil {
|
|
glog.V(logger.Warn).Infof("[CHEQUEBOOK] error depositing %d wei to chequebook (%s, balance: %v, target: %v): %v", amount, self.contract.Hex(), self.balance, self.buffer, err)
|
|
} else {
|
|
self.balance.Add(self.balance, amount)
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] deposited %d wei to chequebook (%s, balance: %v, target: %v)", amount, self.contract.Hex(), self.balance, self.buffer)
|
|
}
|
|
return txhash, err
|
|
}
|
|
|
|
// AutoDeposit(interval, threshold, buffer) (re)sets interval time and amount
|
|
// which triggers sending funds to the chequebook contract
|
|
// backend needs to be set
|
|
// if threshold is not less than buffer, then deposit will be triggered on
|
|
// every new cheque issued
|
|
func (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
self.threshold = threshold
|
|
self.buffer = buffer
|
|
self.autoDeposit(interval)
|
|
}
|
|
|
|
// autoDeposit(interval) starts a go routine that periodically sends funds to the
|
|
// chequebook contract
|
|
// caller holds the lock
|
|
// the go routine terminates if Chequebook.quit us closed
|
|
func (self *Chequebook) autoDeposit(interval time.Duration) {
|
|
if self.quit != nil {
|
|
close(self.quit)
|
|
self.quit = nil
|
|
}
|
|
// if threshold >= balance autodeposit after every cheque issued
|
|
if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 {
|
|
return
|
|
}
|
|
|
|
ticker := time.NewTicker(interval)
|
|
self.quit = make(chan bool)
|
|
quit := self.quit
|
|
go func() {
|
|
FOR:
|
|
for {
|
|
select {
|
|
case <-quit:
|
|
break FOR
|
|
case <-ticker.C:
|
|
self.lock.Lock()
|
|
if self.balance.Cmp(self.buffer) < 0 {
|
|
amount := new(big.Int).Sub(self.buffer, self.balance)
|
|
txhash, err := self.deposit(amount)
|
|
if err == nil {
|
|
self.txhash = txhash
|
|
}
|
|
}
|
|
self.lock.Unlock()
|
|
}
|
|
}
|
|
}()
|
|
return
|
|
}
|
|
|
|
type Outbox struct {
|
|
chequeBook *Chequebook
|
|
beneficiary common.Address
|
|
}
|
|
|
|
func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox {
|
|
return &Outbox{chbook, beneficiary}
|
|
}
|
|
|
|
func (self *Outbox) Issue(amount *big.Int) (swap.Promise, error) {
|
|
return self.chequeBook.Issue(self.beneficiary, amount)
|
|
}
|
|
|
|
func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
|
|
self.chequeBook.AutoDeposit(interval, threshold, buffer)
|
|
}
|
|
|
|
func (self *Outbox) Stop() {}
|
|
|
|
func (self *Outbox) String() string {
|
|
return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance())
|
|
}
|
|
|
|
// type ChequeQueue struct {
|
|
// beneficiary common.Address
|
|
// last map[string]*Inbox
|
|
// }
|
|
|
|
// inbox to deposit, verify and cash cheques
|
|
// from a single contract to single beneficiary
|
|
// incoming payment handler for peer to peer micropayments
|
|
type Inbox struct {
|
|
lock sync.Mutex
|
|
contract common.Address // peer's chequebook contract
|
|
beneficiary common.Address // local peer's receiving address
|
|
sender common.Address // local peer's address to send cashing tx from
|
|
signer *ecdsa.PublicKey // peer's public key
|
|
txhash string // tx hash of last cashing tx
|
|
backend Backend // blockchain API
|
|
quit chan bool // when closed causes autocash to stop
|
|
maxUncashed *big.Int // threshold that triggers autocashing
|
|
cashed *big.Int // cumulative amount cashed
|
|
cheque *Cheque // last cheque, nil if none yet received
|
|
}
|
|
|
|
// NewInbox(contract, beneficiary, signer, backend) constructor for Inbox
|
|
// not persisted, cumulative sum updated from blockchain when first cheque received
|
|
// backend used to sync amount (Call) as well as cash the cheques (Transact)
|
|
func NewInbox(contract, sender, beneficiary common.Address, signer *ecdsa.PublicKey, backend Backend) (self *Inbox, err error) {
|
|
|
|
if signer == nil {
|
|
return nil, fmt.Errorf("signer is null")
|
|
}
|
|
|
|
self = &Inbox{
|
|
contract: contract,
|
|
beneficiary: beneficiary,
|
|
sender: sender,
|
|
signer: signer,
|
|
backend: backend,
|
|
cashed: new(big.Int).Set(common.Big0),
|
|
}
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] initialised inbox (%s -> %s) expected signer: %x", self.contract.Hex(), self.beneficiary.Hex(), crypto.FromECDSAPub(signer))
|
|
return
|
|
}
|
|
|
|
func (self *Inbox) String() string {
|
|
return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount)
|
|
}
|
|
|
|
// Stop() quits the autocash go routine to terminate
|
|
func (self *Inbox) Stop() {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
if self.quit != nil {
|
|
close(self.quit)
|
|
self.quit = nil
|
|
}
|
|
}
|
|
|
|
func (self *Inbox) Cash() (txhash string, err error) {
|
|
if self.cheque != nil {
|
|
txhash, err = self.cheque.Cash(self.sender, self.backend)
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] cashing cheque (total: %v) on chequebook (%s) sending to %v", self.cheque.Amount, self.contract.Hex(), self.beneficiary.Hex())
|
|
self.cashed = self.cheque.Amount
|
|
}
|
|
return
|
|
}
|
|
|
|
// AutoCash(cashInterval, maxUncashed) (re)sets maximum time and amount which
|
|
// triggers cashing of the last uncashed cheque
|
|
// if maxUncashed is set to 0, then autocash on receipt
|
|
func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) {
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
self.maxUncashed = maxUncashed
|
|
self.autoCash(cashInterval)
|
|
}
|
|
|
|
// autoCash(d) starts a loop that periodically clears the last check
|
|
// if the peer is trusted, clearing period could be 24h, or a week
|
|
// caller holds the lock
|
|
func (self *Inbox) autoCash(cashInterval time.Duration) {
|
|
if self.quit != nil {
|
|
close(self.quit)
|
|
self.quit = nil
|
|
}
|
|
// if maxUncashed is set to 0, then autocash on receipt
|
|
if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Cmp(common.Big0) == 0 {
|
|
return
|
|
}
|
|
|
|
ticker := time.NewTicker(cashInterval)
|
|
self.quit = make(chan bool)
|
|
quit := self.quit
|
|
go func() {
|
|
FOR:
|
|
for {
|
|
select {
|
|
case <-quit:
|
|
break FOR
|
|
case <-ticker.C:
|
|
self.lock.Lock()
|
|
if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 {
|
|
txhash, err := self.Cash()
|
|
if err == nil {
|
|
self.txhash = txhash
|
|
}
|
|
}
|
|
self.lock.Unlock()
|
|
}
|
|
}
|
|
}()
|
|
return
|
|
}
|
|
|
|
// Reveive(cheque) called to deposit latest cheque to incoming Inbox
|
|
func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) {
|
|
ch := promise.(*Cheque)
|
|
|
|
defer self.lock.Unlock()
|
|
self.lock.Lock()
|
|
var sum *big.Int
|
|
if self.cheque == nil {
|
|
// the sum is checked against the blockchain once a check is received
|
|
tallyhex, _, err := self.backend.Call(self.beneficiary.Hex(), self.contract.Hex(), "", "", "", getSentAbiEncode(ch.Contract))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err)
|
|
}
|
|
|
|
tally := common.FromHex(tallyhex)
|
|
// var ok bool
|
|
// sum, ok = new(big.Int).SetString(tally, 10)
|
|
// if !ok {
|
|
// return nil, fmt.Errorf("inbox: cannot convert amount '%s' (%v) to integer", tallyhex, tally)
|
|
// }
|
|
sum = new(big.Int).SetBytes(tally)
|
|
} else {
|
|
sum = self.cheque.Amount
|
|
}
|
|
|
|
amount, err := ch.Verify(self.signer, self.contract, self.beneficiary, sum)
|
|
var uncashed *big.Int
|
|
if err == nil {
|
|
self.cheque = ch
|
|
|
|
if self.maxUncashed != nil {
|
|
uncashed = new(big.Int).Sub(ch.Amount, self.cashed)
|
|
if self.maxUncashed.Cmp(uncashed) < 0 {
|
|
self.Cash()
|
|
}
|
|
}
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] received cheque of %v wei in inbox (%s, uncashed: %v)", amount, self.contract.Hex(), uncashed)
|
|
}
|
|
|
|
return amount, err
|
|
}
|
|
|
|
// RSV representation of signature
|
|
func sig2rsv(sig []byte) (v byte, r, s []byte) {
|
|
v = sig[64] + 27
|
|
r = sig[:32]
|
|
s = sig[32:64]
|
|
return
|
|
}
|
|
|
|
func getSentAbiEncode(beneficiary common.Address) string {
|
|
var beneficiary32 [32]byte
|
|
copy(beneficiary32[12:], beneficiary.Bytes())
|
|
return getSentAbiPre + common.Bytes2Hex(beneficiary32[:])
|
|
}
|
|
|
|
// abi encoding of a cheque to send as eth tx data
|
|
func (self *Cheque) cashAbiEncode() string {
|
|
v, r, s := sig2rsv(self.Sig)
|
|
// cashAbiPre, beneficiary, amount, v, r, s
|
|
bigamount := self.Amount.Bytes()
|
|
if len(bigamount) > 32 {
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] number too big: %v (>32 bytes)", self.Amount)
|
|
return ""
|
|
}
|
|
var beneficiary32, amount32, vabi [32]byte
|
|
copy(beneficiary32[12:], self.Beneficiary.Bytes())
|
|
copy(amount32[32-len(bigamount):32], bigamount)
|
|
vabi[31] = v
|
|
return cashAbiPre + common.Bytes2Hex(beneficiary32[:]) + common.Bytes2Hex(amount32[:]) +
|
|
common.Bytes2Hex(vabi[:]) + common.Bytes2Hex(r) + common.Bytes2Hex(s)
|
|
}
|
|
|
|
// Verify(cheque) verifies cheque for signer, contract, beneficiary, amount, valid signature
|
|
func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) {
|
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] verify cheque: %v - sum: %v", self, sum)
|
|
if sum == nil {
|
|
return nil, fmt.Errorf("invalid amount")
|
|
}
|
|
|
|
if self.Beneficiary != beneficiary {
|
|
return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex())
|
|
}
|
|
if self.Contract != contract {
|
|
return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex())
|
|
}
|
|
|
|
amount := new(big.Int).Set(self.Amount)
|
|
if sum != nil {
|
|
amount.Sub(amount, sum)
|
|
if amount.Cmp(common.Big0) <= 0 {
|
|
return nil, fmt.Errorf("incorrect amount: %v <= 0", amount)
|
|
}
|
|
}
|
|
|
|
pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.Sig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid signature: %v", err)
|
|
}
|
|
if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) {
|
|
return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey))
|
|
}
|
|
return amount, nil
|
|
}
|
|
|
|
// Cash(backend) will cash the check using xeth backend to send a transaction
|
|
// Beneficiary address should be unlocked
|
|
func (self *Cheque) Cash(sender common.Address, backend Backend) (string, error) {
|
|
return backend.Transact(sender.Hex(), self.Contract.Hex(), "", "", "", gasToCash, self.cashAbiEncode())
|
|
}
|