diff --git a/api/types.go b/api/types.go index b605bd30..287e04af 100644 --- a/api/types.go +++ b/api/types.go @@ -1,5 +1,7 @@ package api +import "math/big" + type ScriptSig struct { Hex string `json:"hex"` Asm string `json:"asm,omitempty"` @@ -12,8 +14,8 @@ type Vin struct { N int `json:"n"` ScriptSig ScriptSig `json:"scriptSig"` Addr string `json:"addr"` - ValueSat int64 `json:"valueSat"` - Value float64 `json:"value"` + Value string `json:"value"` + ValueSat big.Int `json:"-"` } type ScriptPubKey struct { @@ -23,7 +25,8 @@ type ScriptPubKey struct { Type string `json:"type,omitempty"` } type Vout struct { - Value float64 `json:"value"` + Value string `json:"value"` + ValueSat big.Int `json:"-"` N int `json:"n"` ScriptPubKey ScriptPubKey `json:"scriptPubKey"` SpentTxID string `json:"spentTxId,omitempty"` @@ -32,34 +35,30 @@ type Vout struct { } type Tx struct { - Txid string `json:"txid"` - Version int32 `json:"version,omitempty"` - Locktime uint32 `json:"locktime,omitempty"` - Vin []Vin `json:"vin"` - Vout []Vout `json:"vout"` - Blockhash string `json:"blockhash,omitempty"` - Blockheight int `json:"blockheight"` - Confirmations uint32 `json:"confirmations"` - Time int64 `json:"time,omitempty"` - Blocktime int64 `json:"blocktime"` - ValueOut float64 `json:"valueOut"` - Size int `json:"size,omitempty"` - ValueIn float64 `json:"valueIn"` - Fees float64 `json:"fees"` - WithSpends bool `json:"withSpends,omitempty"` + Txid string `json:"txid"` + Version int32 `json:"version,omitempty"` + Locktime uint32 `json:"locktime,omitempty"` + Vin []Vin `json:"vin"` + Vout []Vout `json:"vout"` + Blockhash string `json:"blockhash,omitempty"` + Blockheight int `json:"blockheight"` + Confirmations uint32 `json:"confirmations"` + Time int64 `json:"time,omitempty"` + Blocktime int64 `json:"blocktime"` + ValueOut string `json:"valueOut"` + Size int `json:"size,omitempty"` + ValueIn string `json:"valueIn"` + Fees string `json:"fees"` + WithSpends bool `json:"withSpends,omitempty"` } type Address struct { - AddrStr string `json:"addrStr"` - Balance float64 `json:"balance"` - BalanceSat int64 `json:"balanceSat"` - TotalReceived float64 `json:"totalReceived"` - TotalReceivedSat int64 `json:"totalReceivedSat"` - TotalSent float64 `json:"totalSent"` - TotalSentSat int64 `json:"totalSentSat"` - UnconfirmedBalance float64 `json:"unconfirmedBalance"` - UnconfirmedBalanceSat int64 `json:"unconfirmedBalanceSat"` - UnconfirmedTxApperances int `json:"unconfirmedTxApperances"` - TxApperances int `json:"txApperances"` - Transactions []*Tx `json:"transactions"` + AddrStr string `json:"addrStr"` + Balance string `json:"balance"` + TotalReceived string `json:"totalReceived"` + TotalSent string `json:"totalSent"` + UnconfirmedBalance string `json:"unconfirmedBalance"` + UnconfirmedTxApperances int `json:"unconfirmedTxApperances"` + TxApperances int `json:"txApperances"` + Transactions []*Tx `json:"transactions"` } diff --git a/api/worker.go b/api/worker.go index 4ad421d3..ea2870e6 100644 --- a/api/worker.go +++ b/api/worker.go @@ -4,6 +4,7 @@ import ( "blockbook/bchain" "blockbook/common" "blockbook/db" + "math/big" "github.com/golang/glog" ) @@ -44,7 +45,7 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) return nil, err } } - var valIn, valOut, fees float64 + var valInSat, valOutSat, feesSat big.Int vins := make([]Vin, len(bchainTx.Vin)) for i := range bchainTx.Vin { bchainVin := &bchainTx.Vin[i] @@ -61,9 +62,9 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) } if len(otx.Vout) > int(vin.Vout) { vout := &otx.Vout[vin.Vout] - vin.Value = vout.Value - valIn += vout.Value - vin.ValueSat = int64(vout.Value*1E8 + 0.5) + vin.ValueSat = vout.ValueSat + vin.Value = w.chainParser.AmountToDecimalString(&vout.ValueSat) + valInSat.Add(&valInSat, &vout.ValueSat) if vout.Address != nil { a := vout.Address.String() vin.Addr = a @@ -76,8 +77,9 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) bchainVout := &bchainTx.Vout[i] vout := &vouts[i] vout.N = i - vout.Value = bchainVout.Value - valOut += bchainVout.Value + vout.ValueSat = bchainVout.ValueSat + vout.Value = w.chainParser.AmountToDecimalString(&bchainVout.ValueSat) + valOutSat.Add(&valOutSat, &bchainVout.ValueSat) vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex vout.ScriptPubKey.Addresses = bchainVout.ScriptPubKey.Addresses if spendingTx { @@ -85,9 +87,9 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) } } // for coinbase transactions valIn is 0 - fees = valIn - valOut - if fees < 0 { - fees = 0 + feesSat.Sub(&valInSat, &valOutSat) + if feesSat.Sign() == -1 { + feesSat.SetUint64(0) } // for now do not return size, we would have to compute vsize of segwit transactions // size:=len(bchainTx.Hex) / 2 @@ -96,13 +98,13 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) Blockheight: int(height), Blocktime: bchainTx.Blocktime, Confirmations: bchainTx.Confirmations, - Fees: fees, + Fees: w.chainParser.AmountToDecimalString(&feesSat), Locktime: bchainTx.LockTime, WithSpends: spendingTx, Time: bchainTx.Time, Txid: bchainTx.Txid, - ValueIn: valIn, - ValueOut: valOut, + ValueIn: w.chainParser.AmountToDecimalString(&valInSat), + ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), Version: bchainTx.Version, Vin: vins, Vout: vouts, @@ -131,26 +133,26 @@ func (s *Worker) getAddressTxids(address string, mempool bool) ([]string, error) return txids, nil } -func (t *Tx) getAddrVoutValue(addrID string) float64 { - var val float64 +func (t *Tx) getAddrVoutValue(addrID string) *big.Int { + var val big.Int for _, vout := range t.Vout { for _, a := range vout.ScriptPubKey.Addresses { if a == addrID { - val += vout.Value + val.Add(&val, &vout.ValueSat) } } } - return val + return &val } -func (t *Tx) getAddrVinValue(addrID string) float64 { - var val float64 +func (t *Tx) getAddrVinValue(addrID string) *big.Int { + var val big.Int for _, vin := range t.Vin { if vin.Addr == addrID { - val += vin.Value + val.Add(&val, &vin.ValueSat) } } - return val + return &val } // UniqueTxidsInReverse reverts the order of transactions (so that newest are first) and removes duplicate transactions @@ -191,14 +193,14 @@ func (w *Worker) GetAddress(addrID string, page int) (*Address, error) { } txs := make([]*Tx, len(txm)+lc) txi := 0 - var uBal, bal, totRecv, totSent float64 + var uBalSat, balSat, totRecvSat, totSentSat big.Int for _, tx := range txm { tx, err := w.GetTransaction(tx, bestheight, false) // mempool transaction may fail if err != nil { glog.Error("GetTransaction ", tx, ": ", err) } else { - uBal = tx.getAddrVoutValue(addrID) - tx.getAddrVinValue(addrID) + uBalSat.Sub(tx.getAddrVoutValue(addrID), tx.getAddrVinValue(addrID)) txs[txi] = tx txi++ } @@ -216,26 +218,23 @@ func (w *Worker) GetAddress(addrID string, page int) (*Address, error) { if err != nil { return nil, err } else { - totRecv += tx.getAddrVoutValue(addrID) - totSent += tx.getAddrVinValue(addrID) + totRecvSat.Add(&totRecvSat, tx.getAddrVoutValue(addrID)) + totSentSat.Add(&totSentSat, tx.getAddrVinValue(addrID)) if i >= from && i < to { txs[txi] = tx txi++ } } } - bal = totRecv - totSent + balSat.Sub(&totRecvSat, &totSentSat) r := &Address{ AddrStr: addrID, - Balance: bal, - BalanceSat: int64(bal*1E8 + 0.5), - TotalReceived: totRecv, - TotalReceivedSat: int64(totRecv*1E8 + 0.5), - TotalSent: totSent, - TotalSentSat: int64(totSent*1E8 + 0.5), + Balance: w.chainParser.AmountToDecimalString(&balSat), + TotalReceived: w.chainParser.AmountToDecimalString(&totRecvSat), + TotalSent: w.chainParser.AmountToDecimalString(&totSentSat), Transactions: txs[:txi], TxApperances: len(txc), - UnconfirmedBalance: uBal, + UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat), UnconfirmedTxApperances: len(txm), } glog.Info(addrID, " finished") diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 8f7bc08a..d8d04fef 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -3,6 +3,8 @@ package bchain import ( "encoding/hex" "encoding/json" + "math/big" + "strings" "github.com/gogo/protobuf/proto" "github.com/juju/errors" @@ -14,6 +16,7 @@ type AddressFactoryFunc func(string) (Address, error) type BaseParser struct { AddressFactory AddressFactoryFunc BlockAddressesToKeep int + AmountDecimalPoint int } // AddressToOutputScript converts address to ScriptPubKey - currently not implemented @@ -36,7 +39,47 @@ func (p *BaseParser) ParseTx(b []byte) (*Tx, error) { return nil, errors.New("ParseTx: not implemented") } -// ParseTxFromJson parses JSON message containing transaction and returs Tx struct +const zeros = "0000000000000000000000000000000000000000" + +// AmountToBigInt converts amount in json.Number (string) to big.Int +// it uses string operations to avoid problems with rounding +func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) { + var r big.Int + s := string(n) + i := strings.IndexByte(s, '.') + if i == -1 { + s = s + zeros[:p.AmountDecimalPoint] + } else { + z := p.AmountDecimalPoint - len(s) + i + 1 + if z > 0 { + s = s[:i] + s[i+1:] + zeros[:z] + } else { + s = s[:i] + s[i+1:len(s)+z] + } + } + if _, ok := r.SetString(s, 10); !ok { + return r, errors.New("AmountToBigInt: failed to convert") + } + return r, nil +} + +// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place +func (p *BaseParser) AmountToDecimalString(a *big.Int) string { + s := a.String() + if len(s) <= p.AmountDecimalPoint { + s = zeros[:p.AmountDecimalPoint-len(s)+1] + s + } + i := len(s) - p.AmountDecimalPoint + ad := strings.TrimRight(s[i:], "0") + if len(ad) > 0 { + s = s[:i] + "." + ad + } else { + s = s[:i] + } + return s +} + +// ParseTxFromJson parses JSON message containing transaction and returns Tx struct func (p *BaseParser) ParseTxFromJson(msg json.RawMessage) (*Tx, error) { var tx Tx err := json.Unmarshal(msg, &tx) @@ -44,13 +87,18 @@ func (p *BaseParser) ParseTxFromJson(msg json.RawMessage) (*Tx, error) { return nil, err } - for i, vout := range tx.Vout { + for i := range tx.Vout { + vout := &tx.Vout[i] + vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue) + if err != nil { + return nil, err + } if len(vout.ScriptPubKey.Addresses) == 1 { a, err := p.AddressFactory(vout.ScriptPubKey.Addresses[0]) if err != nil { return nil, err } - tx.Vout[i].Address = a + vout.Address = a } } @@ -127,7 +175,7 @@ func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, err Addresses: vo.ScriptPubKey.Addresses, N: vo.N, ScriptPubKeyHex: hex, - Value: vo.Value, + ValueSat: vo.ValueSat.Bytes(), } } pt := &ProtoTransaction{ @@ -176,13 +224,15 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) { } vout := make([]Vout, len(pt.Vout)) for i, pto := range pt.Vout { + var vs big.Int + vs.SetBytes(pto.ValueSat) vout[i] = Vout{ N: pto.N, ScriptPubKey: ScriptPubKey{ Addresses: pto.Addresses, Hex: hex.EncodeToString(pto.ScriptPubKeyHex), }, - Value: pto.Value, + ValueSat: vs, } if len(pto.Addresses) == 1 { a, err := p.AddressFactory(pto.Addresses[0]) diff --git a/bchain/coins/bch/bcashrpc.go b/bchain/coins/bch/bcashrpc.go index 68af837a..5dd9b141 100644 --- a/bchain/coins/bch/bcashrpc.go +++ b/bchain/coins/bch/bcashrpc.go @@ -5,6 +5,7 @@ import ( "blockbook/bchain/coins/btc" "encoding/hex" "encoding/json" + "math/big" "github.com/cpacia/bchutil" "github.com/golang/glog" @@ -132,7 +133,7 @@ func (b *BCashRPC) GetBlockFull(hash string) (*bchain.Block, error) { } // EstimateSmartFee returns fee estimation. -func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { +func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { glog.V(1).Info("rpc: estimatesmartfee ", blocks) res := btc.ResEstimateSmartFee{} @@ -141,13 +142,18 @@ func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (float64, err // conservative param is omitted err := b.Call(&req, &res) + var r big.Int if err != nil { - return 0, err + return r, err } if res.Error != nil { - return 0, res.Error + return r, res.Error } - return res.Result.Feerate, nil + r, err = b.Parser.AmountToBigInt(res.Result.Feerate) + if err != nil { + return r, err + } + return r, nil } func isErrBlockNotFound(err *bchain.RPCError) bool { diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index b1611161..d9e8eae4 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/big" "reflect" "time" @@ -172,12 +173,12 @@ func (c *blockChainWithMetrics) GetTransactionForMempool(txid string) (v *bchain return c.b.GetTransactionForMempool(txid) } -func (c *blockChainWithMetrics) EstimateSmartFee(blocks int, conservative bool) (v float64, err error) { +func (c *blockChainWithMetrics) EstimateSmartFee(blocks int, conservative bool) (v big.Int, err error) { defer func(s time.Time) { c.observeRPCLatency("EstimateSmartFee", s, err) }(time.Now()) return c.b.EstimateSmartFee(blocks, conservative) } -func (c *blockChainWithMetrics) EstimateFee(blocks int) (v float64, err error) { +func (c *blockChainWithMetrics) EstimateFee(blocks int) (v big.Int, err error) { defer func(s time.Time) { c.observeRPCLatency("EstimateFee", s, err) }(time.Now()) return c.b.EstimateFee(blocks) } diff --git a/bchain/coins/btc/bitcoinparser.go b/bchain/coins/btc/bitcoinparser.go index 861c7f31..767e8d08 100644 --- a/bchain/coins/btc/bitcoinparser.go +++ b/bchain/coins/btc/bitcoinparser.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "math/big" vlq "github.com/bsm/go-vlq" "github.com/btcsuite/btcd/blockchain" @@ -30,6 +31,7 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser &bchain.BaseParser{ AddressFactory: bchain.NewBaseAddress, BlockAddressesToKeep: c.BlockAddressesToKeep, + AmountDecimalPoint: 8, }, params, outputScriptToAddresses, @@ -123,8 +125,10 @@ func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.T // missing: Asm, // missing: Type, } + var vs big.Int + vs.SetInt64(out.Value) vout[i] = bchain.Vout{ - Value: float64(out.Value) / 1E8, + ValueSat: vs, N: uint32(i), ScriptPubKey: s, } diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index ac482b8b..8f9fb067 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -8,6 +8,7 @@ import ( "encoding/json" "io" "io/ioutil" + "math/big" "net" "net/http" "time" @@ -36,18 +37,20 @@ type BitcoinRPC struct { } type Configuration struct { - CoinName string `json:"coin_name"` - RPCURL string `json:"rpcURL"` - RPCUser string `json:"rpcUser"` - RPCPass string `json:"rpcPass"` - RPCTimeout int `json:"rpcTimeout"` - Parse bool `json:"parse"` - ZeroMQBinding string `json:"zeroMQBinding"` - Subversion string `json:"subversion"` - BlockAddressesToKeep int `json:"blockAddressesToKeep"` - MempoolWorkers int `json:"mempoolWorkers"` - MempoolSubWorkers int `json:"mempoolSubWorkers"` - AddressFormat string `json:"addressFormat"` + CoinName string `json:"coin_name"` + RPCURL string `json:"rpcURL"` + RPCUser string `json:"rpcUser"` + RPCPass string `json:"rpcPass"` + RPCTimeout int `json:"rpcTimeout"` + Parse bool `json:"parse"` + ZeroMQBinding string `json:"zeroMQBinding"` + Subversion string `json:"subversion"` + BlockAddressesToKeep int `json:"blockAddressesToKeep"` + MempoolWorkers int `json:"mempoolWorkers"` + MempoolSubWorkers int `json:"mempoolSubWorkers"` + AddressFormat string `json:"addressFormat"` + SupportsEstimateFee bool `json:"supportsEstimateFee"` + SupportsEstimateSmartFee bool `json:"supportsEstimateSmartFee"` } // NewBitcoinRPC returns new BitcoinRPC instance. @@ -69,6 +72,9 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT if c.MempoolSubWorkers < 1 { c.MempoolSubWorkers = 1 } + // btc supports both calls, other coins overriding BitcoinRPC can change this + c.SupportsEstimateFee = true + c.SupportsEstimateSmartFee = true transport := &http.Transport{ Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial, @@ -301,8 +307,8 @@ type CmdEstimateSmartFee struct { type ResEstimateSmartFee struct { Error *bchain.RPCError `json:"error"` Result struct { - Feerate float64 `json:"feerate"` - Blocks int `json:"blocks"` + Feerate json.Number `json:"feerate"` + Blocks int `json:"blocks"` } `json:"result"` } @@ -317,7 +323,7 @@ type CmdEstimateFee struct { type ResEstimateFee struct { Error *bchain.RPCError `json:"error"` - Result float64 `json:"result"` + Result json.Number `json:"result"` } // sendrawtransaction @@ -616,7 +622,12 @@ func (b *BitcoinRPC) GetMempoolTransactions(address string) ([]string, error) { } // EstimateSmartFee returns fee estimation. -func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { +func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { + // use EstimateFee if EstimateSmartFee is not supported + if !b.ChainConfig.SupportsEstimateSmartFee && b.ChainConfig.SupportsEstimateFee { + return b.EstimateFee(blocks) + } + glog.V(1).Info("rpc: estimatesmartfee ", blocks) res := ResEstimateSmartFee{} @@ -629,17 +640,27 @@ func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, e } err := b.Call(&req, &res) + var r big.Int if err != nil { - return 0, err + return r, err } if res.Error != nil { - return 0, res.Error + return r, res.Error } - return res.Result.Feerate, nil + r, err = b.Parser.AmountToBigInt(res.Result.Feerate) + if err != nil { + return r, err + } + return r, nil } // EstimateFee returns fee estimation. -func (b *BitcoinRPC) EstimateFee(blocks int) (float64, error) { +func (b *BitcoinRPC) EstimateFee(blocks int) (big.Int, error) { + // use EstimateSmartFee if EstimateFee is not supported + if !b.ChainConfig.SupportsEstimateFee && b.ChainConfig.SupportsEstimateSmartFee { + return b.EstimateSmartFee(blocks, true) + } + glog.V(1).Info("rpc: estimatefee ", blocks) res := ResEstimateFee{} @@ -647,13 +668,18 @@ func (b *BitcoinRPC) EstimateFee(blocks int) (float64, error) { req.Params.Blocks = blocks err := b.Call(&req, &res) + var r big.Int if err != nil { - return 0, err + return r, err } if res.Error != nil { - return 0, res.Error + return r, res.Error } - return res.Result, nil + r, err = b.Parser.AmountToBigInt(res.Result) + if err != nil { + return r, err + } + return r, nil } // SendRawTransaction sends raw transaction. diff --git a/bchain/coins/dash/dashrpc.go b/bchain/coins/dash/dashrpc.go index 4c884f08..dec0ee49 100644 --- a/bchain/coins/dash/dashrpc.go +++ b/bchain/coins/dash/dashrpc.go @@ -24,6 +24,7 @@ func NewDashRPC(config json.RawMessage, pushHandler func(bchain.NotificationType b.(*btc.BitcoinRPC), } s.RPCMarshaler = btc.JSONMarshalerV1{} + s.ChainConfig.SupportsEstimateSmartFee = false return s, nil } @@ -53,8 +54,3 @@ func (b *DashRPC) Initialize() error { return nil } - -// EstimateSmartFee returns fee estimation. -func (b *DashRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { - return b.EstimateFee(blocks) -} diff --git a/bchain/coins/dogecoin/dogecoinrpc.go b/bchain/coins/dogecoin/dogecoinrpc.go index d0ebb31c..1fc21618 100644 --- a/bchain/coins/dogecoin/dogecoinrpc.go +++ b/bchain/coins/dogecoin/dogecoinrpc.go @@ -24,6 +24,7 @@ func NewDogecoinRPC(config json.RawMessage, pushHandler func(bchain.Notification b.(*btc.BitcoinRPC), } s.RPCMarshaler = btc.JSONMarshalerV1{} + s.ChainConfig.SupportsEstimateSmartFee = false return s, nil } @@ -69,8 +70,3 @@ func (b *DogecoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error } return b.GetBlockWithoutHeader(hash, height) } - -// EstimateSmartFee returns fee estimation. -func (b *DogecoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { - return b.EstimateFee(blocks) -} diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index fe361258..82d08153 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -22,7 +22,11 @@ type EthereumParser struct { // NewEthereumParser returns new EthereumParser instance func NewEthereumParser() *EthereumParser { - return &EthereumParser{&bchain.BaseParser{AddressFactory: bchain.NewBaseAddress}} + return &EthereumParser{&bchain.BaseParser{ + AddressFactory: bchain.NewBaseAddress, + BlockAddressesToKeep: 0, + AmountDecimalPoint: 18, + }} } type rpcTransaction struct { @@ -86,6 +90,10 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma } tx.BlockHash = bh h := hex.EncodeToString(b) + vs, err := hexutil.DecodeBig(tx.Value) + if err != nil { + return nil, err + } return &bchain.Tx{ Blocktime: blocktime, Confirmations: confirmations, @@ -105,8 +113,8 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma }, Vout: []bchain.Vout{ { - N: 0, // there is always up to one To address - // Value - cannot be set, it does not fit precisely to float64 + N: 0, // there is always up to one To address + ValueSat: *vs, ScriptPubKey: bchain.ScriptPubKey{ // Hex Addresses: ta, @@ -286,9 +294,3 @@ func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) { func (p *EthereumParser) IsUTXOChain() bool { return false } - -// KeepBlockAddresses returns number of blocks which are to be kept in blockaddresses column -// do not use the blockaddresses for eth -func (p *EthereumParser) KeepBlockAddresses() int { - return 0 -} diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index d985e74c..2dda7005 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -478,12 +478,12 @@ func (b *EthereumRPC) GetMempool() ([]string, error) { } // EstimateFee returns fee estimation. -func (b *EthereumRPC) EstimateFee(blocks int) (float64, error) { +func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) { return b.EstimateSmartFee(blocks, true) } // EstimateSmartFee returns fee estimation. -func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (float64, 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 @@ -492,10 +492,12 @@ func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (float64, To: &a, } g, err := b.client.EstimateGas(ctx, msg) + var r big.Int if err != nil { - return 0, err + return r, err } - return float64(g), nil + r.SetUint64(g) + return r, nil } // SendRawTransaction sends raw transaction. diff --git a/bchain/coins/litecoin/litecoinrpc.go b/bchain/coins/litecoin/litecoinrpc.go index 0d2ad15f..e7345251 100644 --- a/bchain/coins/litecoin/litecoinrpc.go +++ b/bchain/coins/litecoin/litecoinrpc.go @@ -24,6 +24,7 @@ func NewLitecoinRPC(config json.RawMessage, pushHandler func(bchain.Notification b.(*btc.BitcoinRPC), } s.RPCMarshaler = btc.JSONMarshalerV2{} + s.ChainConfig.SupportsEstimateFee = false return s, nil } @@ -54,8 +55,3 @@ func (b *LitecoinRPC) Initialize() error { return nil } - -// EstimateFee returns fee estimation. -func (b *LitecoinRPC) EstimateFee(blocks int) (float64, error) { - return b.EstimateSmartFee(blocks, true) -} diff --git a/bchain/coins/namecoin/namecoinrpc.go b/bchain/coins/namecoin/namecoinrpc.go index faa14fd3..6ab0b0aa 100644 --- a/bchain/coins/namecoin/namecoinrpc.go +++ b/bchain/coins/namecoin/namecoinrpc.go @@ -24,6 +24,7 @@ func NewNamecoinRPC(config json.RawMessage, pushHandler func(bchain.Notification b.(*btc.BitcoinRPC), } s.RPCMarshaler = btc.JSONMarshalerV1{} + s.ChainConfig.SupportsEstimateSmartFee = false return s, nil } @@ -69,8 +70,3 @@ func (b *NamecoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error } return b.GetBlockWithoutHeader(hash, height) } - -// EstimateSmartFee returns fee estimation. -func (b *NamecoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { - return b.EstimateFee(blocks) -} diff --git a/bchain/coins/vertcoin/vertcoinrpc.go b/bchain/coins/vertcoin/vertcoinrpc.go index 878b9682..f282dc78 100644 --- a/bchain/coins/vertcoin/vertcoinrpc.go +++ b/bchain/coins/vertcoin/vertcoinrpc.go @@ -24,6 +24,7 @@ func NewVertcoinRPC(config json.RawMessage, pushHandler func(bchain.Notification b.(*btc.BitcoinRPC), } s.RPCMarshaler = btc.JSONMarshalerV2{} + s.ChainConfig.SupportsEstimateFee = false return s, nil } @@ -54,8 +55,3 @@ func (b *VertcoinRPC) Initialize() error { return nil } - -// EstimateFee returns fee estimation. -func (b *VertcoinRPC) EstimateFee(blocks int) (float64, error) { - return b.EstimateSmartFee(blocks, true) -} diff --git a/bchain/coins/zec/zcashrpc.go b/bchain/coins/zec/zcashrpc.go index 495fa7af..b2c45fe5 100644 --- a/bchain/coins/zec/zcashrpc.go +++ b/bchain/coins/zec/zcashrpc.go @@ -22,6 +22,7 @@ func NewZCashRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp BitcoinRPC: b.(*btc.BitcoinRPC), } z.RPCMarshaler = btc.JSONMarshalerV1{} + z.ChainConfig.SupportsEstimateSmartFee = false return z, nil } @@ -114,14 +115,6 @@ func (z *ZCashRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { return z.GetTransaction(txid) } -// EstimateSmartFee returns fee estimation. -func (z *ZCashRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { - glog.V(1).Info("rpc: estimatesmartfee") - - // return z.estimateFee(blocks) - return z.EstimateFee(blocks) -} - // GetMempoolEntry returns mempool data for given transaction func (z *ZCashRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) { return nil, errors.New("GetMempoolEntry: not implemented") diff --git a/bchain/tx.pb.go b/bchain/tx.pb.go index dc5a0952..2115993a 100644 --- a/bchain/tx.pb.go +++ b/bchain/tx.pb.go @@ -148,7 +148,7 @@ func (m *ProtoTransaction_VinType) GetAddresses() []string { } type ProtoTransaction_VoutType struct { - Value float64 `protobuf:"fixed64,1,opt,name=Value" json:"Value,omitempty"` + ValueSat []byte `protobuf:"bytes,1,opt,name=ValueSat,proto3" json:"ValueSat,omitempty"` N uint32 `protobuf:"varint,2,opt,name=N" json:"N,omitempty"` ScriptPubKeyHex []byte `protobuf:"bytes,3,opt,name=ScriptPubKeyHex,proto3" json:"ScriptPubKeyHex,omitempty"` Addresses []string `protobuf:"bytes,4,rep,name=Addresses" json:"Addresses,omitempty"` @@ -159,11 +159,11 @@ func (m *ProtoTransaction_VoutType) String() string { return proto.Co func (*ProtoTransaction_VoutType) ProtoMessage() {} func (*ProtoTransaction_VoutType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 1} } -func (m *ProtoTransaction_VoutType) GetValue() float64 { +func (m *ProtoTransaction_VoutType) GetValueSat() []byte { if m != nil { - return m.Value + return m.ValueSat } - return 0 + return nil } func (m *ProtoTransaction_VoutType) GetN() uint32 { @@ -196,26 +196,26 @@ func init() { func init() { proto.RegisterFile("tx.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 330 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xc1, 0x4e, 0xc2, 0x40, - 0x10, 0x86, 0xb3, 0xb4, 0x14, 0x18, 0x21, 0x92, 0x89, 0x31, 0x0d, 0xf1, 0x50, 0x39, 0xf5, 0xd4, - 0x03, 0xc6, 0x07, 0x50, 0x2f, 0x24, 0x1a, 0x42, 0x16, 0xd2, 0x7b, 0x5b, 0x36, 0xb0, 0x11, 0x77, - 0xb1, 0xdd, 0x1a, 0x78, 0x16, 0x1f, 0xc1, 0x97, 0x34, 0x3b, 0x2d, 0x45, 0x48, 0xbc, 0xed, 0xff, - 0xef, 0x4c, 0xff, 0x6f, 0xff, 0x14, 0xba, 0x66, 0x1f, 0xed, 0x72, 0x6d, 0x34, 0x7a, 0x69, 0xb6, - 0x49, 0xa4, 0x1a, 0x7f, 0xbb, 0x30, 0x9c, 0x5b, 0x67, 0x99, 0x27, 0xaa, 0x48, 0x32, 0x23, 0xb5, - 0x42, 0x04, 0x77, 0xb9, 0x97, 0x2b, 0x9f, 0x05, 0x2c, 0xec, 0x73, 0x3a, 0xe3, 0x10, 0x9c, 0xa9, - 0xd8, 0xfb, 0x2d, 0xb2, 0xec, 0x11, 0xef, 0xa0, 0xf7, 0xbc, 0xd5, 0xd9, 0xbb, 0x91, 0x1f, 0xc2, - 0x77, 0x02, 0x16, 0xba, 0xfc, 0x64, 0xe0, 0x08, 0xba, 0x6f, 0xc7, 0x4b, 0x37, 0x60, 0xe1, 0x80, - 0x37, 0x1a, 0x6f, 0xc1, 0x9b, 0x0a, 0xb9, 0xde, 0x18, 0xbf, 0x4d, 0x37, 0xb5, 0xc2, 0x09, 0x38, - 0xb1, 0x54, 0xbe, 0x17, 0x38, 0xe1, 0xd5, 0x24, 0x88, 0x2a, 0xc4, 0xe8, 0x12, 0x2f, 0x8a, 0xa5, - 0x5a, 0x1e, 0x76, 0x82, 0xdb, 0x61, 0x7c, 0x04, 0x37, 0xd6, 0xa5, 0xf1, 0x3b, 0xb4, 0x74, 0xff, - 0xff, 0x92, 0x2e, 0x0d, 0x6d, 0xd1, 0xf8, 0xe8, 0x87, 0x41, 0xa7, 0xfe, 0x8e, 0x45, 0x7d, 0xd1, - 0x52, 0xa5, 0x49, 0x21, 0xe8, 0xc9, 0x3d, 0xde, 0xe8, 0xa6, 0x8a, 0xd6, 0x9f, 0x2a, 0xb0, 0x8e, - 0x74, 0x08, 0x9e, 0xce, 0x38, 0x86, 0xfe, 0x22, 0xcb, 0xe5, 0xce, 0x2c, 0xe4, 0xda, 0xf6, 0xe4, - 0xd2, 0xfc, 0x99, 0x67, 0x73, 0x16, 0xe2, 0xb3, 0x14, 0x2a, 0x13, 0xf5, 0xc3, 0x1b, 0x6d, 0xcb, - 0x7c, 0x5a, 0xad, 0x72, 0x51, 0x14, 0xa2, 0xa0, 0x02, 0x7a, 0xfc, 0x64, 0x8c, 0xbe, 0xa0, 0x7b, - 0xe4, 0xc7, 0x1b, 0x68, 0xc7, 0xc9, 0xb6, 0xac, 0x50, 0x19, 0xaf, 0x04, 0xf6, 0x81, 0xcd, 0x08, - 0x72, 0xc0, 0xd9, 0x0c, 0x43, 0xb8, 0xae, 0x92, 0xe7, 0x65, 0xfa, 0x2a, 0x0e, 0x16, 0xc8, 0x21, - 0xa0, 0x4b, 0xfb, 0x3c, 0xd7, 0xbd, 0xc8, 0x4d, 0x3d, 0xfa, 0x59, 0x1e, 0x7e, 0x03, 0x00, 0x00, - 0xff, 0xff, 0x64, 0x1d, 0x38, 0xac, 0x38, 0x02, 0x00, 0x00, + // 331 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0xc1, 0x6e, 0xea, 0x30, + 0x10, 0x94, 0x89, 0x5f, 0x80, 0x7d, 0xa0, 0x87, 0xf6, 0xf0, 0x14, 0xa1, 0x1e, 0x52, 0x4e, 0x39, + 0xe5, 0x40, 0xd5, 0x0f, 0x68, 0x7b, 0x41, 0x6a, 0x85, 0x90, 0x83, 0x72, 0x4f, 0x82, 0x05, 0x56, + 0xa9, 0x4d, 0x13, 0x47, 0x02, 0xa9, 0x3f, 0xd3, 0x73, 0x7f, 0xb2, 0xf2, 0x12, 0x42, 0x41, 0xea, + 0x6d, 0x67, 0xbc, 0xe3, 0x19, 0x4f, 0x02, 0x3d, 0xbb, 0x8f, 0x77, 0xa5, 0xb1, 0x06, 0xfd, 0xbc, + 0xd8, 0x64, 0x4a, 0x4f, 0x3e, 0x39, 0x8c, 0x16, 0x8e, 0x59, 0x96, 0x99, 0xae, 0xb2, 0xc2, 0x2a, + 0xa3, 0x11, 0x81, 0x2f, 0xf7, 0x6a, 0x15, 0xb0, 0x90, 0x45, 0x03, 0x41, 0x33, 0x8e, 0xc0, 0x9b, + 0xc9, 0x7d, 0xd0, 0x21, 0xca, 0x8d, 0x78, 0x03, 0xfd, 0xc7, 0xad, 0x29, 0x5e, 0xad, 0x7a, 0x93, + 0x81, 0x17, 0xb2, 0x88, 0x8b, 0x33, 0x81, 0x63, 0xe8, 0xbd, 0x9c, 0x0e, 0x79, 0xc8, 0xa2, 0xa1, + 0x68, 0x31, 0xfe, 0x07, 0x7f, 0x26, 0xd5, 0x7a, 0x63, 0x83, 0x3f, 0x74, 0xd2, 0x20, 0x9c, 0x82, + 0x97, 0x2a, 0x1d, 0xf8, 0xa1, 0x17, 0xfd, 0x9d, 0x86, 0xf1, 0x31, 0x62, 0x7c, 0x1d, 0x2f, 0x4e, + 0x95, 0x5e, 0x1e, 0x76, 0x52, 0xb8, 0x65, 0xbc, 0x07, 0x9e, 0x9a, 0xda, 0x06, 0x5d, 0x12, 0xdd, + 0xfe, 0x2e, 0x32, 0xb5, 0x25, 0x15, 0xad, 0x8f, 0xbf, 0x18, 0x74, 0x9b, 0x7b, 0x5c, 0xd4, 0x27, + 0xa3, 0x74, 0x9e, 0x55, 0x92, 0x9e, 0xdc, 0x17, 0x2d, 0x6e, 0xab, 0xe8, 0xfc, 0xa8, 0x02, 0x1b, + 0x4b, 0x8f, 0xc2, 0xd3, 0x8c, 0x13, 0x18, 0x24, 0x45, 0xa9, 0x76, 0x36, 0x51, 0x6b, 0xd7, 0x13, + 0xa7, 0xfd, 0x0b, 0xce, 0xf9, 0x24, 0xf2, 0xbd, 0x96, 0xba, 0x90, 0xcd, 0xc3, 0x5b, 0xec, 0xca, + 0x7c, 0x58, 0xad, 0x4a, 0x59, 0x55, 0xb2, 0xa2, 0x02, 0xfa, 0xe2, 0x4c, 0x8c, 0x3f, 0xa0, 0x77, + 0xca, 0xef, 0x6e, 0x49, 0xb3, 0x6d, 0x2d, 0x93, 0xcc, 0x36, 0x1f, 0xa8, 0xc5, 0x38, 0x00, 0x36, + 0xa7, 0xa8, 0x43, 0xc1, 0xe6, 0x18, 0xc1, 0xbf, 0xa3, 0xff, 0xa2, 0xce, 0x9f, 0xe5, 0xc1, 0xc5, + 0xf2, 0x48, 0x70, 0x4d, 0x5f, 0xba, 0xf3, 0x2b, 0xf7, 0xdc, 0xa7, 0x5f, 0xe6, 0xee, 0x3b, 0x00, + 0x00, 0xff, 0xff, 0x95, 0x9e, 0x00, 0x7a, 0x3e, 0x02, 0x00, 0x00, } diff --git a/bchain/tx.proto b/bchain/tx.proto index 731b8bf5..61e06aed 100644 --- a/bchain/tx.proto +++ b/bchain/tx.proto @@ -11,7 +11,7 @@ syntax = "proto3"; repeated string Addresses = 6; } message VoutType { - double Value = 1; + bytes ValueSat = 1; uint32 N = 2; bytes ScriptPubKeyHex = 3; repeated string Addresses = 4; diff --git a/bchain/types.go b/bchain/types.go index e2aa7276..758f15f9 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" ) // errors with specific meaning returned by blockchain rpc @@ -49,7 +50,8 @@ type Address interface { } type Vout struct { - Value float64 `json:"value"` + ValueSat big.Int + JsonValue json.Number `json:"value"` N uint32 `json:"n"` ScriptPubKey ScriptPubKey `json:"scriptPubKey"` Address Address @@ -90,9 +92,9 @@ type BlockHeader struct { type MempoolEntry struct { Size uint32 `json:"size"` - Fee float64 `json:"fee"` - ModifiedFee float64 `json:"modifiedfee"` - Time float64 `json:"time"` + Fee big.Int `json:"fee"` + ModifiedFee big.Int `json:"modifiedfee"` + Time uint64 `json:"time"` Height uint32 `json:"height"` DescendantCount uint32 `json:"descendantcount"` DescendantSize uint32 `json:"descendantsize"` @@ -132,8 +134,8 @@ type BlockChain interface { GetMempool() ([]string, error) GetTransaction(txid string) (*Tx, error) GetTransactionForMempool(txid string) (*Tx, error) - EstimateSmartFee(blocks int, conservative bool) (float64, error) - EstimateFee(blocks int) (float64, error) + EstimateSmartFee(blocks int, conservative bool) (big.Int, error) + EstimateFee(blocks int) (big.Int, error) SendRawTransaction(tx string) (string, error) // mempool ResyncMempool(onNewTxAddr func(txid string, addr string)) (int, error) @@ -145,7 +147,7 @@ type BlockChain interface { // BlockChainParser defines common interface to parsing and conversions of block chain data type BlockChainParser interface { - // self description + // chain configuration description // UTXO chains need "inputs" column in db, that map transactions to transactions that spend them // non UTXO chains have mapping of address to input and output transactions directly in "outputs" column in db IsUTXOChain() bool @@ -153,6 +155,11 @@ type BlockChainParser interface { // and used in case of fork // if 0 the blockaddresses column is not used at all (usually non UTXO chains) KeepBlockAddresses() int + // AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place + AmountToDecimalString(a *big.Int) string + // AmountToBigInt converts amount in json.Number (string) to big.Int + // it uses string operations to avoid problems with rounding + AmountToBigInt(n json.Number) (big.Int, error) // address id conversions GetAddrIDFromVout(output *Vout) ([]byte, error) GetAddrIDFromAddress(address string) ([]byte, error) diff --git a/server/public.go b/server/public.go index 54555325..acafd08c 100644 --- a/server/public.go +++ b/server/public.go @@ -113,8 +113,10 @@ func formatUnixTime(ut int64) string { return time.Unix(ut, 0).Format(time.RFC1123) } -func formatAmount(a float64) string { - return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%0.8f", a), "0"), ".") +// for now return the string as it is +// in future could be used to do coin specific formatting +func formatAmount(a string) string { + return a } // Run starts the server diff --git a/server/socketio.go b/server/socketio.go index 46c0a563..74fbd67b 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -7,6 +7,7 @@ import ( "blockbook/db" "encoding/json" "net/http" + "strconv" "strings" "time" @@ -236,11 +237,11 @@ type txInputs struct { // ScriptAsm *string `json:"scriptAsm"` Sequence int64 `json:"sequence"` Address *string `json:"address"` - Satoshis int64 `json:"satoshis"` + Satoshis string `json:"satoshis"` } type txOutputs struct { - Satoshis int64 `json:"satoshis"` + Satoshis string `json:"satoshis"` Script *string `json:"script"` // ScriptAsm *string `json:"scriptAsm"` SpentTxID *string `json:"spentTxId,omitempty"` @@ -267,7 +268,7 @@ type resTx struct { type addressHistoryItem struct { Addresses map[string]addressHistoryIndexes `json:"addresses"` - Satoshis int64 `json:"satoshis"` + Satoshis string `json:"satoshis"` Confirmations int `json:"confirmations"` Tx resTx `json:"tx"` } @@ -329,7 +330,7 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r for _, vout := range tx.Vout { aoh := vout.ScriptPubKey.Hex ao := txOutputs{ - Satoshis: int64(vout.Value*1E8 + 0.5), + Satoshis: vout.ValueSat.String(), Script: &aoh, } if vout.Address != nil { @@ -452,6 +453,7 @@ func unmarshalEstimateSmartFee(params []byte) (blocks int, conservative bool, er } type resultEstimateSmartFee struct { + // for compatibility reasons use float64 Result float64 `json:"result"` } @@ -460,7 +462,7 @@ func (s *SocketIoServer) estimateSmartFee(blocks int, conservative bool) (res re if err != nil { return } - res.Result = fee + res.Result, err = strconv.ParseFloat(s.chainParser.AmountToDecimalString(&fee), 64) return } @@ -479,6 +481,7 @@ func unmarshalEstimateFee(params []byte) (blocks int, err error) { } type resultEstimateFee struct { + // for compatibility reasons use float64 Result float64 `json:"result"` } @@ -487,7 +490,7 @@ func (s *SocketIoServer) estimateFee(blocks int) (res resultEstimateFee, err err if err != nil { return } - res.Result = fee + res.Result, err = strconv.ParseFloat(s.chainParser.AmountToDecimalString(&fee), 64) return } @@ -588,7 +591,7 @@ func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetai a := vout.Address.String() ai.Address = &a } - ai.Satoshis = int64(vout.Value*1E8 + 0.5) + ai.Satoshis = vout.ValueSat.String() } } hi = append(hi, ai) @@ -596,7 +599,7 @@ func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetai for _, vout := range tx.Vout { aos := vout.ScriptPubKey.Hex ao := txOutputs{ - Satoshis: int64(vout.Value*1E8 + 0.5), + Satoshis: vout.ValueSat.String(), Script: &aos, } if vout.Address != nil {