From 39f2c73f3e87012b787c7929055c61925840d525 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Tue, 23 Jul 2019 12:52:18 +0200 Subject: [PATCH] Add coinbase flag (boolean) to UTXO response #236 --- api/types.go | 1 + api/worker.go | 20 +++++++++++++++++++- bchain/baseparser.go | 5 +++++ bchain/coins/btc/bitcoinparser.go | 29 ++++++++++++++++++----------- bchain/coins/btc/bitcoinrpc.go | 5 +++++ bchain/types.go | 2 ++ docs/api.md | 10 ++++++++++ 7 files changed, 60 insertions(+), 12 deletions(-) diff --git a/api/types.go b/api/types.go index 54302ca1..33b94d39 100644 --- a/api/types.go +++ b/api/types.go @@ -266,6 +266,7 @@ type Utxo struct { Address string `json:"address,omitempty"` Path string `json:"path,omitempty"` Locktime uint32 `json:"lockTime,omitempty"` + Coinbase bool `json:"coinbase,omitempty"` } // Utxos is array of Utxo diff --git a/api/worker.go b/api/worker.go index 2649973b..29f19e2b 100644 --- a/api/worker.go +++ b/api/worker.go @@ -843,11 +843,16 @@ func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrB // report only outpoints that are not spent in mempool _, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)] if !e { + coinbase := false + if len(bchainTx.Vin) == 1 && len(bchainTx.Vin[0].Coinbase) > 0 { + coinbase = true + } r = append(r, Utxo{ Txid: bchainTx.Txid, Vout: int32(i), AmountSat: (*Amount)(&vout.ValueSat), Locktime: bchainTx.LockTime, + Coinbase: coinbase, }) } } @@ -882,12 +887,25 @@ func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrB } _, e := spentInMempool[txid+strconv.Itoa(int(utxo.Vout))] if !e { + confirmations := bestheight - int(utxo.Height) + 1 + coinbase := false + // for performance reasons, check coinbase transactions only in minimim confirmantion range + if confirmations < w.chainParser.MinimumCoinbaseConfirmations() { + ta, err := w.db.GetTxAddresses(txid) + if err != nil { + return nil, err + } + if len(ta.Inputs) == 1 && len(ta.Inputs[0].AddrDesc) == 0 && IsZeroBigInt(&ta.Inputs[0].ValueSat) { + coinbase = true + } + } r = append(r, Utxo{ Txid: txid, Vout: utxo.Vout, AmountSat: (*Amount)(&utxo.ValueSat), Height: int(utxo.Height), - Confirmations: bestheight - int(utxo.Height) + 1, + Confirmations: confirmations, + Coinbase: coinbase, }) } checksum.Sub(&checksum, &utxo.ValueSat) diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 90d5c5a2..d3596b82 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -161,6 +161,11 @@ func (p *BaseParser) GetChainType() ChainType { return ChainBitcoinType } +// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent +func (p *BaseParser) MinimumCoinbaseConfirmations() int { + return 0 +} + // PackTx packs transaction to byte array using protobuf func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error) { var err error diff --git a/bchain/coins/btc/bitcoinparser.go b/bchain/coins/btc/bitcoinparser.go index 24d236ef..a6206ba4 100644 --- a/bchain/coins/btc/bitcoinparser.go +++ b/bchain/coins/btc/bitcoinparser.go @@ -24,12 +24,13 @@ type OutputScriptToAddressesFunc func(script []byte) ([]string, bool, error) // BitcoinParser handle type BitcoinParser struct { *bchain.BaseParser - Params *chaincfg.Params - OutputScriptToAddressesFunc OutputScriptToAddressesFunc - XPubMagic uint32 - XPubMagicSegwitP2sh uint32 - XPubMagicSegwitNative uint32 - Slip44 uint32 + Params *chaincfg.Params + OutputScriptToAddressesFunc OutputScriptToAddressesFunc + XPubMagic uint32 + XPubMagicSegwitP2sh uint32 + XPubMagicSegwitNative uint32 + Slip44 uint32 + minimumCoinbaseConfirmations int } // NewBitcoinParser returns new BitcoinParser instance @@ -39,11 +40,12 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser BlockAddressesToKeep: c.BlockAddressesToKeep, AmountDecimalPoint: 8, }, - Params: params, - XPubMagic: c.XPubMagic, - XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh, - XPubMagicSegwitNative: c.XPubMagicSegwitNative, - Slip44: c.Slip44, + Params: params, + XPubMagic: c.XPubMagic, + XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh, + XPubMagicSegwitNative: c.XPubMagicSegwitNative, + Slip44: c.Slip44, + minimumCoinbaseConfirmations: c.MinimumCoinbaseConfirmations, } p.OutputScriptToAddressesFunc = p.outputScriptToAddresses return p @@ -326,6 +328,11 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { return tx, height, nil } +// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent +func (p *BitcoinParser) MinimumCoinbaseConfirmations() int { + return p.minimumCoinbaseConfirmations +} + func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) { var a btcutil.Address var err error diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 1f36e556..ef50c710 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -57,6 +57,7 @@ type Configuration struct { Slip44 uint32 `json:"slip44,omitempty"` AlternativeEstimateFee string `json:"alternativeEstimateFee,omitempty"` AlternativeEstimateFeeParams string `json:"alternativeEstimateFeeParams,omitempty"` + MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"` } // NewBitcoinRPC returns new BitcoinRPC instance. @@ -71,6 +72,10 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT if c.BlockAddressesToKeep < 100 { c.BlockAddressesToKeep = 100 } + // default MinimumCoinbaseConfirmations is 100 + if c.MinimumCoinbaseConfirmations == 0 { + c.MinimumCoinbaseConfirmations = 100 + } // at least 1 mempool worker/subworker for synchronous mempool synchronization if c.MempoolWorkers < 1 { c.MempoolWorkers = 1 diff --git a/bchain/types.go b/bchain/types.go index 7e87fa74..dd397a51 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -254,6 +254,8 @@ type BlockChainParser interface { KeepBlockAddresses() int // AmountDecimals returns number of decimal places in coin amounts AmountDecimals() int + // MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent + MinimumCoinbaseConfirmations() 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 diff --git a/docs/api.md b/docs/api.md index 393816d1..1dacaebe 100644 --- a/docs/api.md +++ b/docs/api.md @@ -407,6 +407,8 @@ Returns array of unspent transaction outputs of address or xpub, applicable only Unconfirmed utxos do not have field *height*, the field *confirmations* has value *0* and may contain field *lockTime*, if not zero. +Coinbase utxos do have field *coinbase* set to true, however due to performance reasons only up to minimum coinbase confirmations limit (100). After this limit, utxos are not detected as coinbase. + ``` GET /api/v2/utxo/[?confirmed=true] ``` @@ -422,6 +424,14 @@ Response: "confirmations": 0, "lockTime": 2648100 }, + { + "txid": "a79e396a32e10856c97b95f43da7e9d2b9a11d446f7638dbd75e5e7603128cac", + "vout": 1, + "value": "39748685", + "height": 2648043, + "confirmations": 47, + "coinbase": true + }, { "txid": "de4f379fdc3ea9be063e60340461a014f372a018d70c3db35701654e7066b3ef", "vout": 0,