线上直接转账需要一定的费用,如果存在大量小额交易的情况下,费用会变的难以承受,因而以太坊引入了微交易支付通道来解决这个问题。以太坊提供了一个票据支付方案,主要依赖于智能合约实现的一对多的账单系统。该账单系统大致上的执行流程如下.
1:账单生成,同时提供机制往账单上存入保证金。
2:交易发起人生成交易票据
3:直接将票据发送给接收人
4:接收人兑现票据,从合约转账(尽管某次兑现可能会失败,但是只要票据存在最终还是能够兑现).
这种交易优点在于可以在线下发送任意数量的交易,而只需要两个链上交易(存入保证金,兑现)只要存入保证金,线下通过交换票据进行任意数量的交易,避免了频繁的线上转账,节省了交易费用。
.
├── api.go //对外接口
├── cheque.go //账单
├── cheque_test.go
├── contract
│ ├── chequebook.go //合约go语言接口
│ ├── chequebook.sol //合约源码
│ ├── code.go //合约byte码
│ ├── mortal.sol //合约销毁
│ └── owned.sol //hebe权限
└── gencode.go //合约byte码生成
合约自身是接收转账的,用户可以在初始化或者后来增加金额,可以通过cash方法兑现票据,转账金额都会保存在send变量上。
pragma solidity ^0.4.18;
import "./mortal.sol";
/// @title Chequebook for Ethereum micropayments
/// @author Daniel A. Nagy
contract chequebook is mortal {
// Cumulative paid amount in wei to each beneficiary
//已经支付的 可以控制双花,防止多次兑换票据
mapping (address => uint256) public sent;
/// @notice Overdraft event
event Overdraft(address deadbeat);
// Allow sending ether to the chequebook.
function() public payable { }
/// @notice Cash cheque
///
/// @param beneficiary beneficiary address
/// @param amount cumulative amount in wei
/// @param sig_v signature parameter v
/// @param sig_r signature parameter r
/// @param sig_s signature parameter s
/// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount
function cash(address beneficiary, uint256 amount, uint8 sig_v, bytes32 sig_r, bytes32 sig_s) public {
// Check if the cheque is old.
// Only cheques that are more recent than the last cashed one are considered.
require(amount > sent[beneficiary]);
// Check the digital signature of the cheque.
bytes32 hash = keccak256(address(this), beneficiary, amount);
require(owner == ecrecover(hash, sig_v, sig_r, sig_s));
// Attempt sending the difference between the cumulative amount on the cheque
// and the cumulative amount on the last cashed cheque to beneficiary.
uint256 diff = amount - sent[beneficiary];
if (diff <= this.balance) {
// update the cumulative amount before sending
sent[beneficiary] = amount;
beneficiary.transfer(diff);
} else {
// Upon failure, punish owner for writing a bounced cheque.
// owner.sendToDebtorsPrison();
Overdraft(owner);
// Compensate beneficiary.
selfdestruct(beneficiary);
}
}
}
账单保存了账本的位置,记账人,所有人等信
// Chequebook can create and sign cheques from a single contract to multiple beneficiaries.
// It is the 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)
contract *contract.Chequebook // abigen binding
session *contract.ChequebookSession // abigen binding with Tx Opts
// persisted fields
balance *big.Int // not synced with blockchain
contractAddr common.Address // contract address
sent map[common.Address]*big.Int //tallies for beneficiaries
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
log log.Logger // contextual logger with the contract address embedded
}
票据:合约位置,接收人,金额,签名
type Cheque struct {
Contract common.Address // address of chequebook, needed to avoid cross-contract submission
Beneficiary common.Address
Amount *big.Int // cumulative amount of all funds sent
Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey)
}
生成一条支付记录,返回一份签名后的票据,收费这凭借这张票据从合约里面取钱.
// Issue creates a cheque 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.Sign() <= 0 {
return nil, fmt.Errorf("amount must be greater than zero (%v)", amount)
}
if self.balance.Cmp(amount) < 0 {
err = fmt.Errorf("insufficient 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.contractAddr, beneficiary, sum), self.prvKey)
if err == nil {
ch = &Cheque{
Contract: self.contractAddr,
Beneficiary: beneficiary,
Amount: sum,
Sig: sig,
}
sent.Set(sum)
self.balance.Sub(self.balance, amount) // subtract amount from balance
}
}
// 账单余额少于阈值,自动补充.
if self.threshold != nil {
if self.balance.Cmp(self.threshold) < 0 {
send := new(big.Int).Sub(self.buffer, self.balance)
self.deposit(send)
}
}
return
}
func (self *Chequebook) Deposit(amount *big.Int) (string, error) {
defer self.lock.Unlock()
self.lock.Lock()
return self.deposit(amount)
}
func (self *Chequebook) deposit(amount *big.Int) (string, error) {
// since the amount is variable here, we do not use sessions
depositTransactor := bind.NewKeyedTransactor(self.prvKey)
depositTransactor.Value = amount
chbookRaw := &contract.ChequebookRaw{Contract: self.contract}
//转入金额
tx, err := chbookRaw.Transfer(depositTransactor)
if err != nil {
self.log.Warn("Failed to fund chequebook", "amount", amount, "balance", self.balance, "target", self.buffer, "err", err)
return "", err
}
// assume that transaction is actually successful, we add the amount to balance right away
self.balance.Add(self.balance, amount)
self.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", self.balance, "target", self.buffer)
return tx.Hash().Hex(), nil
}
// Cash is a convenience method to cash any cheque.
func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) {
return ch.Cash(self.session)
}
// Cash cashes the cheque by sending an Ethereum transaction.
func (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) {
v, r, s := sig2vrs(self.Sig)
//调用合约的cash方法 提取代币
tx, err := session.Cash(self.Beneficiary, self.Amount, v, r, s)
if err != nil {
return "", err
}
return tx.Hash().Hex(), nil
}
OutBox:用于在电对点网络中发行票据,提供了保证金存入,票据发行,自动存入保证金等接口。
type Outbox struct {
chequeBook *Chequebook
beneficiary common.Address
}
// Issue creates cheque.
func (self *Outbox) Issue(amount *big.Int) (swap.Promise, error) {
return self.chequeBook.Issue(self.beneficiary, amount)
}
// AutoDeposit enables auto-deposits on the underlying chequebook.
func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
self.chequeBook.AutoDeposit(interval, threshold, buffer)
}
InBox:用于在电对点网络中票据兑换,提供了直接兑换,定时兑换,延迟兑换的接口功能。
// Inbox can deposit, verify and cash cheques from a single contract to a single
// beneficiary. It is the 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
session *contract.ChequebookSession // abi contract backend with tx opts
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
log log.Logger // contextual logger with the contract address embedded
}
// Cash attempts to cash the current cheque.
func (self *Inbox) Cash() (txhash string, err error) {
if self.cheque != nil {
txhash, err = self.cheque.Cash(self.session)
self.log.Trace("Cashing in chequebook cheque", "amount", self.cheque.Amount, "beneficiary", self.beneficiary)
self.cashed = self.cheque.Amount
}
return
}
转自:(魂祭心) https://my.oschina.net/hunjixin/blog/1807446
如果你希望高效的学习以太坊DApp开发,可以访问汇智网提供的最热门在线互动教程:
1.适合区块链新手的以太坊DApp实战入门教程
2.区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问这个以太坊博客