-
Notifications
You must be signed in to change notification settings - Fork 110
Swap available balance #1892
Swap available balance #1892
Changes from all commits
f0431f4
de5b152
18500a9
d11b4b3
d4e5abc
1ff69ff
6eb0a57
523d220
075c322
7807019
e0cab6a
203dbec
f52e515
57416dd
4011ffe
80296ac
cacb39f
ed886ab
5bc0c28
c11f840
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ package swap | |
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"math/big" | ||
"time" | ||
|
||
|
@@ -44,8 +45,14 @@ type Backend interface { | |
|
||
// Contract interface defines the methods exported from the underlying go-bindings for the smart contract | ||
type Contract interface { | ||
// Withdraw attempts to withdraw Wei from the chequebook | ||
Withdraw(auth *bind.TransactOpts, backend Backend, amount *big.Int) (*types.Receipt, error) | ||
// Deposit sends a raw transaction to the chequebook, triggering the fallback—depositing amount | ||
Deposit(auth *bind.TransactOpts, backend Backend, amout *big.Int) (*types.Receipt, error) | ||
// CashChequeBeneficiary cashes the cheque by the beneficiary | ||
CashChequeBeneficiary(auth *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error) | ||
// LiquidBalance returns the LiquidBalance (total balance in Wei - total hard deposits in Wei) of the chequebook | ||
LiquidBalance(auth *bind.CallOpts) (*big.Int, error) | ||
// ContractParams returns contract info (e.g. deployed address) | ||
ContractParams() *Params | ||
// Issuer returns the contract owner from the blockchain | ||
|
@@ -97,6 +104,32 @@ func InstanceAt(address common.Address, backend Backend) (Contract, error) { | |
return c, err | ||
} | ||
|
||
// Withdraw withdraws amount from the chequebook and blocks until the transaction is mined | ||
func (s simpleContract) Withdraw(auth *bind.TransactOpts, backend Backend, amount *big.Int) (*types.Receipt, error) { | ||
tx, err := s.instance.Withdraw(auth, amount) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return WaitFunc(auth, backend, tx) | ||
} | ||
|
||
// Deposit sends a transaction to the chequebook, which deposits the amount set in Auth.Value and blocks until the transaction is mined | ||
func (s simpleContract) Deposit(auth *bind.TransactOpts, backend Backend, amount *big.Int) (*types.Receipt, error) { | ||
rawSimpleSwap := contract.SimpleSwapRaw{Contract: s.instance} | ||
if auth.Value != big.NewInt(0) { | ||
return nil, fmt.Errorf("Deposit value can only be set via amount parameter") | ||
} | ||
if amount == big.NewInt(0) { | ||
return nil, fmt.Errorf("Deposit amount cannot be equal to zero") | ||
} | ||
auth.Value = amount | ||
mortelli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tx, err := rawSimpleSwap.Transfer(auth) | ||
mortelli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return nil, err | ||
} | ||
return WaitFunc(auth, backend, tx) | ||
} | ||
|
||
// CashChequeBeneficiary cashes the cheque on the blockchain and blocks until the transaction is mined. | ||
func (s simpleContract) CashChequeBeneficiary(opts *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error) { | ||
tx, err := s.instance.CashChequeBeneficiary(opts, beneficiary, cumulativePayout, ownerSig) | ||
|
@@ -131,6 +164,11 @@ func (s simpleContract) CashChequeBeneficiary(opts *bind.TransactOpts, beneficia | |
return result, receipt, nil | ||
} | ||
|
||
// LiquidBalance returns the LiquidBalance (total balance in Wei - total hard deposits in Wei) of the chequebook | ||
func (s simpleContract) LiquidBalance(opts *bind.CallOpts) (*big.Int, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment exported func There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
return s.instance.LiquidBalance(opts) | ||
} | ||
|
||
// ContractParams returns contract information | ||
func (s simpleContract) ContractParams() *Params { | ||
return &Params{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,15 +53,15 @@ const swapLogLevel = 3 // swapLogLevel indicates filter level of log messages | |
// A node maintains an individual balance with every peer | ||
// Only messages which have a price will be accounted for | ||
type Swap struct { | ||
store state.Store // store is needed in order to keep balances and cheques across sessions | ||
peers map[enode.ID]*Peer // map of all swap Peers | ||
peersLock sync.RWMutex // lock for peers map | ||
backend contract.Backend // the backend (blockchain) used | ||
owner *Owner // contract access | ||
params *Params // economic and operational parameters | ||
contract swap.Contract // reference to the smart contract | ||
chequebookFactory swap.SimpleSwapFactory // the chequebook factory used | ||
honeyPriceOracle HoneyOracle // oracle which resolves the price of honey (in Wei) | ||
store state.Store // store is needed in order to keep balances and cheques across sessions | ||
peers map[enode.ID]*Peer // map of all swap Peers | ||
peersLock sync.RWMutex // lock for peers map | ||
backend contract.Backend // the backend (blockchain) used | ||
owner *Owner // contract access | ||
params *Params // economic and operational parameters | ||
contract contract.Contract // reference to the smart contract | ||
chequebookFactory contract.SimpleSwapFactory // the chequebook factory used | ||
honeyPriceOracle HoneyOracle // oracle which resolves the price of honey (in Wei) | ||
} | ||
|
||
// Owner encapsulates information related to accessing the contract | ||
|
@@ -179,6 +179,7 @@ func New(dbPath string, prvkey *ecdsa.PrivateKey, backendURL string, params *Par | |
return nil, err | ||
} | ||
swapLog.Info("Using backend network ID", "ID", chainID.Uint64()) | ||
|
||
// create the owner of SWAP | ||
owner := createOwner(prvkey) | ||
// initialize the factory | ||
|
@@ -199,6 +200,13 @@ func New(dbPath string, prvkey *ecdsa.PrivateKey, backendURL string, params *Par | |
if swap.contract, err = swap.StartChequebook(chequebookAddressFlag, initialDepositAmountFlag); err != nil { | ||
return nil, err | ||
} | ||
availableBalance, err := swap.AvailableBalance() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
swapLog.Info("available balance", "balance", availableBalance) | ||
|
||
return swap, nil | ||
} | ||
|
||
|
@@ -461,6 +469,37 @@ func (s *Swap) Balances() (map[enode.ID]int64, error) { | |
return balances, nil | ||
} | ||
|
||
// AvailableBalance returns the total balance of the chequebook against which new cheques can be written | ||
func (s *Swap) AvailableBalance() (uint64, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would really recommend avoiding 2 blockchain wide event filters in one function, this can take quite a while on a real network (especially if using remote endpoints like infura). Instead we could compute this per peer using If we need this for all peers (not sure why we would though) we can compute If we implement it that way we also don't need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Ralph, this call is needed for a node operator to figure out how much balance is still left in his chequebook against which new cheques can be written. It allows him to figure out what his spendable balance is and when he has to re-deposit. Furthermore, this call is needed by Swarm in order to verify that an to-be-send cheque is not overspending. I will see how and if I can incorporate your proposed formula. Thanks for the suggestion! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Ralph, If we would go for implementing your (second) formula instead of how it is implemented now with the event filters, we need to do a call to the smart-contract to read paidOut for every peer. Is this indeed quicker than the event filter? Note that Infura recently updated how they handle event queries: https://blog.infura.io/faster-logs-and-events-e43e2fa13773 If you think that what you propose is indeed much quicker, I will make the update! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: the option you presented and the one which is currently implemented are not entirely the same. They deviate in the case where a cheque is sent outside of Swarm and already cashed. The current implementation would recognize this (and calculate the available balance correctly from the point of cashing onwards), while your proposal would never recognize this cheque as it is not cashed by a peer, hence the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not consider the case with cheques outside of Swarm, this is always a user error and if done other things will break anyway. If we want the chequebook to be able to be used for other purposes we should introduce an RPC call for that so that the actual cheque creation and sending still takes place in the Swarm node. Blockchain-wide event queries (from block 0 to "latest") can be really slow in my experience (Have you tested this call against ropsten or mainnet?). The Infura optimisation doesn't help much if it is then still slow for other users. (Since it's only being called once now it's not as big of a problem anymore). While true that my formula does require more calls to The current approach also doesn't update There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would compute If we want this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great discussion guys! i don't understand the issue as in-depth as you two, but at a high level, it seems sensible to me to query the source of the data (in this case the blockchain) for the latest info when it's needed. but this, of course, is contingent upon these queries not slowing down the system significantly. it might be hard to verify this. is the alternative (proposed by @Eknir) slower overall? or is it just less robust/more complicated? i understand the query from the genesis block to the present is slow according to @ralph-pichler, but it would be less queries in the long run, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mortelli , it's an interesting discussion indeed. At this stage, I would personally favor robustness over efficiency, as we can always make efficiency gains later on, but robustness flaws are probably less nice to detect in a live network. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I may put my thoughts here. I pretty much have to agree with @ralph-pichler.
As @ralph-pichler says these events are horribly slow. We've been using them in Giveth and at one point it took 1.5hs to reprocess everything (around 5000 events). And this is after optimizations like querying since the contract deployment etc. Sure here it will not be that extreme in most of the cases but we should at minimum test it. And yeah chain reorgs or connection issues created a lot of errors and had to be handled.
I also agree here. At least for the immediate future, I don't think we should be too bothered by this case. We should make an issue about this but not actively solve it now. One of the usecases I can imagine is when user runs multiple swarm nodes with one chequebook. Still this brings a lot of problems to solve on the accounting side because it introduces all sorts of concurrency issues. |
||
// get the LiquidBalance of the chequebook | ||
liquidBalance, err := s.contract.LiquidBalance(nil) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
// get all sent Cheques | ||
sentCheques, err := s.SentCheques() | ||
mortelli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
// Compute the total worth of cheques sent and how much of of this is cashed | ||
var sentChequesWorth uint64 | ||
var cashedChequesWorth uint64 | ||
for _, ch := range sentCheques { | ||
if ch == nil { | ||
continue | ||
} | ||
sentChequesWorth += ch.ChequeParams.CumulativePayout | ||
paidOut, err := s.contract.PaidOut(nil, ch.ChequeParams.Beneficiary) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't need to be changed in this PR but we really need to start thinking about contexts and timeouts when doing blockchain interaction. Here we call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please comment on the issue @ralph-pichler , with your recommendation for setting |
||
if err != nil { | ||
return 0, err | ||
} | ||
cashedChequesWorth += paidOut.Uint64() | ||
} | ||
return liquidBalance.Uint64() + cashedChequesWorth - sentChequesWorth, nil | ||
} | ||
|
||
// SentCheque returns the last sent cheque for a given peer | ||
func (s *Swap) SentCheque(peer enode.ID) (cheque *Cheque, err error) { | ||
if swapPeer := s.getPeer(peer); swapPeer != nil { | ||
|
@@ -594,7 +633,7 @@ func (s *Swap) Close() error { | |
} | ||
|
||
// GetParams returns contract parameters (Bin, ABI, contractAddress) from the contract | ||
func (s *Swap) GetParams() *swap.Params { | ||
func (s *Swap) GetParams() *contract.Params { | ||
mortelli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return s.contract.ContractParams() | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.