以太坊微支付通道原理与实现
线上直接转账需要一定的费用,如果存在大量小额交易的情况下,费用会变的难以承受,因而以太坊引入了微交易支付通道来解决这个问题。以太坊提供了一个票据支付方案,主要依赖于智能合约实现的一对多的账单系统。该账单系统大致上的执行流程如下.
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 <daniel@ethereum.org>
-
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
-
}