Add estimateFee method to websocket interface

This commit is contained in:
Martin Boehm 2018-12-18 09:52:46 +01:00
parent 83f9a525b0
commit 7edea80209
6 changed files with 165 additions and 77 deletions

View File

@ -39,6 +39,11 @@ func (b *BaseChain) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, er
return 0, errors.New("Not supported")
}
// EthereumTypeEstimateGas is not supported
func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
return 0, errors.New("Not supported")
}
// EthereumTypeGetErc20ContractInfo is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) {
return nil, errors.New("Not supported")

View File

@ -252,6 +252,11 @@ func (c *blockChainWithMetrics) EthereumTypeGetNonce(addrDesc bchain.AddressDesc
return c.b.EthereumTypeGetNonce(addrDesc)
}
func (c *blockChainWithMetrics) EthereumTypeEstimateGas(params map[string]interface{}) (v uint64, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeEstimateGas", s, err) }(time.Now())
return c.b.EthereumTypeEstimateGas(params)
}
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractInfo(contractDesc)

View File

@ -632,18 +632,42 @@ func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) {
func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
// TODO - what parameters of msg to use to get better estimate, maybe more data from the wallet are needed
a := ethcommon.HexToAddress("0x1234567890123456789012345678901234567890")
msg := ethereum.CallMsg{
To: &a,
}
g, err := b.client.EstimateGas(ctx, msg)
var r big.Int
if err != nil {
return r, err
gp, err := b.client.SuggestGasPrice(ctx)
if err == nil && b != nil {
r = *gp
}
r.SetUint64(g)
return r, nil
return r, err
}
func getStringFromMap(p string, params map[string]interface{}) (string, bool) {
v, ok := params[p]
if ok {
s, ok := v.(string)
return s, ok
}
return "", false
}
// EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters
func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
msg := ethereum.CallMsg{}
s, ok := getStringFromMap("from", params)
if ok && len(s) > 0 {
msg.From = ethcommon.HexToAddress(s)
}
s, ok = getStringFromMap("to", params)
if ok && len(s) > 0 {
a := ethcommon.HexToAddress(s)
msg.To = &a
}
s, ok = getStringFromMap("data", params)
if ok && len(s) > 0 {
msg.Data = ethcommon.FromHex(s)
}
return b.client.EstimateGas(ctx, msg)
}
// SendRawTransaction sends raw transaction

View File

@ -216,6 +216,7 @@ type BlockChain interface {
// EthereumType specific
EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error)
EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error)
EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error)
EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error)
EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error)
}

View File

@ -6,8 +6,10 @@ import (
"blockbook/common"
"blockbook/db"
"encoding/json"
"math/big"
"net/http"
"runtime/debug"
"strconv"
"sync"
"sync/atomic"
"time"
@ -225,6 +227,9 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
"getInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
return s.getInfo()
},
"estimateFee": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
return s.estimateFee(c, req.Params)
},
"sendTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Hex string `json:"hex"`
@ -360,6 +365,72 @@ func (s *WebsocketServer) getInfo() (interface{}, error) {
}, nil
}
func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) {
type estimateFeeReq struct {
Blocks []int `json:"blocks"`
Specific map[string]interface{} `json:"specific"`
}
type estimateFeeRes struct {
FeePerTx string `json:"feePerTx,omitempty"`
FeePerUnit string `json:"feePerUnit,omitempty"`
FeeLimit string `json:"feeLimit,omitempty"`
}
var r estimateFeeReq
err := json.Unmarshal(params, &r)
if err != nil {
return nil, err
}
res := make([]estimateFeeRes, len(r.Blocks))
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
gas, err := s.chain.EthereumTypeEstimateGas(r.Specific)
if err != nil {
return nil, err
}
sg := strconv.FormatUint(gas, 10)
for i, b := range r.Blocks {
fee, err := s.chain.EstimateSmartFee(b, true)
if err != nil {
return nil, err
}
res[i].FeePerUnit = fee.String()
res[i].FeeLimit = sg
fee.Mul(&fee, new(big.Int).SetUint64(gas))
res[i].FeePerTx = fee.String()
}
} else {
conservative := true
v, ok := r.Specific["conservative"]
if ok {
vc, ok := v.(bool)
if ok {
conservative = vc
}
}
txSize := 0
v, ok = r.Specific["txsize"]
if ok {
f, ok := v.(float64)
if ok {
txSize = int(f)
}
}
for i, b := range r.Blocks {
fee, err := s.chain.EstimateSmartFee(b, conservative)
if err != nil {
return nil, err
}
res[i].FeePerUnit = fee.String()
if txSize > 0 {
fee.Mul(&fee, big.NewInt(int64(txSize)))
fee.Add(&fee, big.NewInt(500))
fee.Div(&fee, big.NewInt(1000))
res[i].FeePerTx = fee.String()
}
}
}
return res, nil
}
func (s *WebsocketServer) sendTransaction(tx string) (res resultSendTransaction, err error) {
txid, err := s.chain.SendRawTransaction(tx)
if err != nil {
@ -461,7 +532,7 @@ func (s *WebsocketServer) OnNewBlock(hash string, height uint32) {
// OnNewTxAddr is a callback that broadcasts info about a tx affecting subscribed address
func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor, isOutput bool) {
// check if there is any subscription but release lock immediately, GetTransactionFromBchainTx may take some time
// check if there is any subscription but release the lock immediately, GetTransactionFromBchainTx may take some time
s.addressSubscriptionsLock.Lock()
as, ok := s.addressSubscriptions[string(addrDesc)]
s.addressSubscriptionsLock.Unlock()

View File

@ -97,6 +97,15 @@
};
}
function getInfo() {
const method = 'getInfo';
const params = {
};
send(method, params, function (result) {
document.getElementById('getInfoResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
});
}
function getAccountInfo() {
const descriptor = document.getElementById('getAccountInfoDescriptor').value.trim();
const selectDetails = document.getElementById('getAccountInfoDetails');
@ -121,13 +130,31 @@
});
}
function getInfo() {
const method = 'getInfo';
const params = {
};
send(method, params, function (result) {
document.getElementById('getInfoResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
});
function estimateFee() {
try {
var blocks = document.getElementById('estimateFeeBlocks').value.split(",");
var specific = document.getElementById('estimateFeeSpecific').value.trim();
if (specific) {
// example for bitcoin type: {"conservative": false,"txsize":1234}
// example for ethereum type: {"from":"0x65513ecd11fd3a5b1fefdcc6a500b025008405a2","to":"0x65513ecd11fd3a5b1fefdcc6a500b025008405a2","data":"0xabcd"}
specific = JSON.parse(specific)
}
else {
specific = undefined;
}
blocks = blocks.map(s => parseInt(s.trim()));
const method = 'estimateFee';
const params = {
blocks,
specific
};
send(method, params, function (result) {
document.getElementById('estimateFeeResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
});
}
catch (e) {
document.getElementById('estimateFeeResult').innerText = e;
}
}
function sendTransaction() {
@ -198,41 +225,6 @@
});
}
function getBlockHeader() {
var param = document.getElementById('getBlockHeaderParam').value.trim();
lookupBlockHash(isHash(param) ? param : parseInt(param), function (result) {
console.log('getBlockHeader sent successfully');
console.log(result);
document.getElementById('getBlockHeaderResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
});
}
function isHash(str) {
var re = /[0-9A-Fa-f]{64}/g;
return re.test(str);
}
function lookupBlockHash(heightOrHash, f) {
const method = 'getBlockHeader';
const params = [heightOrHash];
return socket.send({ method, params }, f);
}
function estimateFee() {
var blocks = document.getElementById('estimateFeeBlocks').value.trim();
estimateTxFee(parseInt(blocks), function (result) {
console.log('estimateFee sent successfully');
console.log(result);
document.getElementById('estimateFeeResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
});
}
function estimateTxFee(blocks, f) {
const method = 'estimateFee';
const params = [blocks];
return socket.send({ method, params }, f);
}
</script>
</head>
@ -252,6 +244,13 @@
<label id="connectionStatus">not connected</label>
</div>
</div>
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="getInfo" onclick="getInfo()">
</div>
<div class="col-10" id="getInfoResult">
</div>
</div>
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="getAccountInfo" onclick="getAccountInfo()">
@ -279,41 +278,24 @@
<div class="col" id="getAccountInfoResult">
</div>
</div>
<!--
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="getBlockHeader" onclick="getBlockHeader()">
</div>
<div class="col-8">
<input type="text" class="form-control" id="getBlockHeaderParam" value="0">
</div>
<div class="col">
</div>
</div>
<div class="row">
<div class="col" id="getBlockHeaderResult">
</div>
</div>
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="estimateFee" onclick="estimateFee()">
</div>
<div class="col-8">
<input type="text" class="form-control" id="estimateFeeBlocks" value="20">
<div class="row" style="margin: 0;">
<input type="text" placeholder="comma separated list of block targets" class="form-control" id="estimateFeeBlocks" value="2,5,10,20">
</div>
<div class="row" style="margin: 0; margin-top: 5px;">
<input type="text" placeholder="tx specific JSON" class="form-control" id="estimateFeeSpecific" value="">
</div>
</div>
<div class="col"></div>
</div>
<div class="row">
<div class="col" id="estimateFeeResult">
</div>
</div>-->
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="getInfo" onclick="getInfo()">
</div>
<div class="col-10" id="getInfoResult">
</div>
</div>
</div>
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="sendTransaction" onclick="sendTransaction()">