Add estimateFee method to websocket interface
This commit is contained in:
parent
83f9a525b0
commit
7edea80209
@ -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")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user