Compare commits

..

No commits in common. "master" and "v0.22.112" have entirely different histories.

28 changed files with 389 additions and 1637 deletions

View file

@ -18,7 +18,7 @@ builds:
ldflags: ldflags:
- -s -w - -s -w
- -buildid= - -buildid=
- -X github.com/lbryfoundation/lbcd/version.appTag={{ .Tag }} - -X github.com/lbryio/lbcd/version.appTag={{ .Tag }}
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64 - linux_arm64
@ -35,7 +35,7 @@ builds:
ldflags: ldflags:
- -s -w - -s -w
- -buildid= - -buildid=
- -X github.com/lbryfoundation/lbcd/version.appTag={{ .Tag }} - -X github.com/lbryio/lbcd/version.appTag={{ .Tag }}
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
targets: targets:
@ -60,8 +60,8 @@ dockers:
- use: buildx - use: buildx
dockerfile: Dockerfile.goreleaser dockerfile: Dockerfile.goreleaser
image_templates: image_templates:
- "docker.io/lbryfoundation/lbcd:{{ .Tag }}" - "docker.io/lbry/lbcd:{{ .Tag }}"
- "docker.io/lbryfoundation/lbcd:latest" - "docker.io/lbry/lbcd:latest"
release: release:
draft: true draft: true

View file

@ -35,37 +35,35 @@ type GetBlockHeaderVerboseResult struct {
} }
// GetBlockStatsResult models the data from the getblockstats command. // GetBlockStatsResult models the data from the getblockstats command.
// Pointers are used instead of values to allow for optional fields.
type GetBlockStatsResult struct { type GetBlockStatsResult struct {
AverageFee *int64 `json:"avgfee,omitempty"` AverageFee int64 `json:"avgfee"`
AverageFeeRate *int64 `json:"avgfeerate,omitempty"` AverageFeeRate int64 `json:"avgfeerate"`
AverageTxSize *int64 `json:"avgtxsize,omitempty"` AverageTxSize int64 `json:"avgtxsize"`
FeeratePercentiles *[]int64 `json:"feerate_percentiles,omitempty"` FeeratePercentiles []int64 `json:"feerate_percentiles"`
Hash *string `json:"blockhash,omitempty"` Hash string `json:"blockhash"`
Height *int64 `json:"height,omitempty"` Height int64 `json:"height"`
Ins *int64 `json:"ins,omitempty"` Ins int64 `json:"ins"`
MaxFee *int64 `json:"maxfee,omitempty"` MaxFee int64 `json:"maxfee"`
MaxFeeRate *int64 `json:"maxfeerate,omitempty"` MaxFeeRate int64 `json:"maxfeerate"`
MaxTxSize *int64 `json:"maxtxsize,omitempty"` MaxTxSize int64 `json:"maxtxsize"`
MedianFee *int64 `json:"medianfee,omitempty"` MedianFee int64 `json:"medianfee"`
MedianTime *int64 `json:"mediantime,omitempty"` MedianTime int64 `json:"mediantime"`
MedianTxSize *int64 `json:"mediantxsize,omitempty"` MedianTxSize int64 `json:"mediantxsize"`
MinFee *int64 `json:"minfee,omitempty"` MinFee int64 `json:"minfee"`
MinFeeRate *int64 `json:"minfeerate,omitempty"` MinFeeRate int64 `json:"minfeerate"`
MinTxSize *int64 `json:"mintxsize,omitempty"` MinTxSize int64 `json:"mintxsize"`
Outs *int64 `json:"outs,omitempty"` Outs int64 `json:"outs"`
SegWitTotalSize *int64 `json:"swtotal_size,omitempty"` SegWitTotalSize int64 `json:"swtotal_size"`
SegWitTotalWeight *int64 `json:"swtotal_weight,omitempty"` SegWitTotalWeight int64 `json:"swtotal_weight"`
SegWitTxs *int64 `json:"swtxs,omitempty"` SegWitTxs int64 `json:"swtxs"`
Subsidy *int64 `json:"subsidy,omitempty"` Subsidy int64 `json:"subsidy"`
Time *int64 `json:"time,omitempty"` Time int64 `json:"time"`
TotalOut *int64 `json:"total_out,omitempty"` TotalOut int64 `json:"total_out"`
TotalSize *int64 `json:"total_size,omitempty"` TotalSize int64 `json:"total_size"`
TotalWeight *int64 `json:"total_weight,omitempty"` TotalWeight int64 `json:"total_weight"`
TotalFee *int64 `json:"totalfee,omitempty"` Txs int64 `json:"txs"`
Txs *int64 `json:"txs,omitempty"` UTXOIncrease int64 `json:"utxo_increase"`
UTXOIncrease *int64 `json:"utxo_increase,omitempty"` UTXOSizeIncrease int64 `json:"utxo_size_inc"`
UTXOSizeIncrease *int64 `json:"utxo_size_inc,omitempty"`
} }
type GetBlockVerboseResultBase struct { type GetBlockVerboseResultBase struct {

View file

@ -176,13 +176,12 @@ func NewGetAccountCmd(address string) *GetAccountCmd {
// GetAccountAddressCmd defines the getaccountaddress JSON-RPC command. // GetAccountAddressCmd defines the getaccountaddress JSON-RPC command.
type GetAccountAddressCmd struct { type GetAccountAddressCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account string
AddressType *string `jsonrpcdefault:"\"legacy\""`
} }
// NewGetAccountAddressCmd returns a new instance which can be used to issue a // NewGetAccountAddressCmd returns a new instance which can be used to issue a
// getaccountaddress JSON-RPC command. // getaccountaddress JSON-RPC command.
func NewGetAccountAddressCmd(account *string) *GetAccountAddressCmd { func NewGetAccountAddressCmd(account string) *GetAccountAddressCmd {
return &GetAccountAddressCmd{ return &GetAccountAddressCmd{
Account: account, Account: account,
} }
@ -190,13 +189,12 @@ func NewGetAccountAddressCmd(account *string) *GetAccountAddressCmd {
// GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command. // GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command.
type GetAddressesByAccountCmd struct { type GetAddressesByAccountCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account string
AddressType *string `jsonrpcdefault:"\"*\""`
} }
// NewGetAddressesByAccountCmd returns a new instance which can be used to issue // NewGetAddressesByAccountCmd returns a new instance which can be used to issue
// a getaddressesbyaccount JSON-RPC command. // a getaddressesbyaccount JSON-RPC command.
func NewGetAddressesByAccountCmd(account *string) *GetAddressesByAccountCmd { func NewGetAddressesByAccountCmd(account string) *GetAddressesByAccountCmd {
return &GetAddressesByAccountCmd{ return &GetAddressesByAccountCmd{
Account: account, Account: account,
} }
@ -217,9 +215,8 @@ func NewGetAddressInfoCmd(address string) *GetAddressInfoCmd {
// GetBalanceCmd defines the getbalance JSON-RPC command. // GetBalanceCmd defines the getbalance JSON-RPC command.
type GetBalanceCmd struct { type GetBalanceCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account *string
MinConf *int `jsonrpcdefault:"1"` MinConf *int `jsonrpcdefault:"1"`
AddressType *string `jsonrpcdefault:"\"*\""`
} }
// NewGetBalanceCmd returns a new instance which can be used to issue a // NewGetBalanceCmd returns a new instance which can be used to issue a
@ -245,8 +242,8 @@ func NewGetBalancesCmd() *GetBalancesCmd {
// GetNewAddressCmd defines the getnewaddress JSON-RPC command. // GetNewAddressCmd defines the getnewaddress JSON-RPC command.
type GetNewAddressCmd struct { type GetNewAddressCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account *string
AddressType *string `jsonrpcdefault:"\"legacy\""` AddressType *string // must be one of legacy / p2pkh or p2sh-p2wkh / p2sh-segwit, or p2wkh / bech32
} }
// NewGetNewAddressCmd returns a new instance which can be used to issue a // NewGetNewAddressCmd returns a new instance which can be used to issue a
@ -262,8 +259,7 @@ func NewGetNewAddressCmd(account *string) *GetNewAddressCmd {
// GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command. // GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command.
type GetRawChangeAddressCmd struct { type GetRawChangeAddressCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account *string
AddressType *string `jsonrpcdefault:"\"legacy\""`
} }
// NewGetRawChangeAddressCmd returns a new instance which can be used to issue a // NewGetRawChangeAddressCmd returns a new instance which can be used to issue a
@ -279,8 +275,8 @@ func NewGetRawChangeAddressCmd(account *string) *GetRawChangeAddressCmd {
// GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command. // GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command.
type GetReceivedByAccountCmd struct { type GetReceivedByAccountCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account string
MinConf *int `jsonrpcdefault:"1"` MinConf *int `jsonrpcdefault:"1"`
} }
// NewGetReceivedByAccountCmd returns a new instance which can be used to issue // NewGetReceivedByAccountCmd returns a new instance which can be used to issue
@ -288,7 +284,7 @@ type GetReceivedByAccountCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // for optional parameters will use the default value.
func NewGetReceivedByAccountCmd(account *string, minConf *int) *GetReceivedByAccountCmd { func NewGetReceivedByAccountCmd(account string, minConf *int) *GetReceivedByAccountCmd {
return &GetReceivedByAccountCmd{ return &GetReceivedByAccountCmd{
Account: account, Account: account,
MinConf: minConf, MinConf: minConf,
@ -411,8 +407,7 @@ func NewKeyPoolRefillCmd(newSize *uint) *KeyPoolRefillCmd {
// ListAccountsCmd defines the listaccounts JSON-RPC command. // ListAccountsCmd defines the listaccounts JSON-RPC command.
type ListAccountsCmd struct { type ListAccountsCmd struct {
MinConf *int `jsonrpcdefault:"1"` MinConf *int `jsonrpcdefault:"1"`
AddressType *string `jsonrpcdefault:"\"*\""`
} }
// NewListAccountsCmd returns a new instance which can be used to issue a // NewListAccountsCmd returns a new instance which can be used to issue a
@ -506,10 +501,10 @@ func NewListSinceBlockCmd(blockHash *string, targetConfirms *int, includeWatchOn
// ListTransactionsCmd defines the listtransactions JSON-RPC command. // ListTransactionsCmd defines the listtransactions JSON-RPC command.
type ListTransactionsCmd struct { type ListTransactionsCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account *string
Count *int `jsonrpcdefault:"10"` Count *int `jsonrpcdefault:"10"`
From *int `jsonrpcdefault:"0"` From *int `jsonrpcdefault:"0"`
IncludeWatchOnly *bool `jsonrpcdefault:"false"` IncludeWatchOnly *bool `jsonrpcdefault:"false"`
} }
// NewListTransactionsCmd returns a new instance which can be used to issue a // NewListTransactionsCmd returns a new instance which can be used to issue a
@ -567,7 +562,6 @@ type SendFromCmd struct {
ToAddress string ToAddress string
Amount float64 // In BTC Amount float64 // In BTC
MinConf *int `jsonrpcdefault:"1"` MinConf *int `jsonrpcdefault:"1"`
AddressType *string `jsonrpcdefault:"\"*\""`
Comment *string Comment *string
CommentTo *string CommentTo *string
} }
@ -577,15 +571,12 @@ type SendFromCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // for optional parameters will use the default value.
func NewSendFromCmd(fromAccount, toAddress string, amount float64, func NewSendFromCmd(fromAccount, toAddress string, amount float64, minConf *int, comment, commentTo *string) *SendFromCmd {
minConf *int, addrType *string, comment, commentTo *string) *SendFromCmd {
return &SendFromCmd{ return &SendFromCmd{
FromAccount: fromAccount, FromAccount: fromAccount,
ToAddress: toAddress, ToAddress: toAddress,
Amount: amount, Amount: amount,
MinConf: minConf, MinConf: minConf,
AddressType: addrType,
Comment: comment, Comment: comment,
CommentTo: commentTo, CommentTo: commentTo,
} }
@ -596,7 +587,6 @@ type SendManyCmd struct {
FromAccount string FromAccount string
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
MinConf *int `jsonrpcdefault:"1"` MinConf *int `jsonrpcdefault:"1"`
AddressType *string `jsonrpcdefault:"\"*\""`
Comment *string Comment *string
} }
@ -605,24 +595,21 @@ type SendManyCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // for optional parameters will use the default value.
func NewSendManyCmd(fromAccount string, amounts map[string]float64, func NewSendManyCmd(fromAccount string, amounts map[string]float64, minConf *int, comment *string) *SendManyCmd {
minConf *int, addrType *string, comment *string) *SendManyCmd {
return &SendManyCmd{ return &SendManyCmd{
FromAccount: fromAccount, FromAccount: fromAccount,
Amounts: amounts, Amounts: amounts,
MinConf: minConf, MinConf: minConf,
AddressType: addrType,
Comment: comment, Comment: comment,
} }
} }
// SendToAddressCmd defines the sendtoaddress JSON-RPC command. // SendToAddressCmd defines the sendtoaddress JSON-RPC command.
type SendToAddressCmd struct { type SendToAddressCmd struct {
Address string Address string
Amount float64 Amount float64
AddressType *string `jsonrpcdefault:"\"*\""` Comment *string
Comment *string CommentTo *string
CommentTo *string
} }
// NewSendToAddressCmd returns a new instance which can be used to issue a // NewSendToAddressCmd returns a new instance which can be used to issue a
@ -630,14 +617,12 @@ type SendToAddressCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // for optional parameters will use the default value.
func NewSendToAddressCmd(address string, amount float64, addrType *string, func NewSendToAddressCmd(address string, amount float64, comment, commentTo *string) *SendToAddressCmd {
comment, commentTo *string) *SendToAddressCmd {
return &SendToAddressCmd{ return &SendToAddressCmd{
Address: address, Address: address,
Amount: amount, Amount: amount,
AddressType: addrType, Comment: comment,
Comment: comment, CommentTo: commentTo,
CommentTo: commentTo,
} }
} }
@ -977,8 +962,8 @@ func NewImportMultiCmd(requests []ImportMultiRequest, options *ImportMultiOption
// RescanBlockchainCmd defines the RescanBlockchain JSON-RPC command. // RescanBlockchainCmd defines the RescanBlockchain JSON-RPC command.
type RescanBlockchainCmd struct { type RescanBlockchainCmd struct {
StartHeight *int32 `jsonrpcdefault:"0"` StartHeight *int64 `jsonrpcdefault:"0"`
StopHeight *int32 StopHeight *int64 `jsonrpcdefault:"0"`
} }
// NewRescanBlockchainCmd returns a new instance which can be used to issue // NewRescanBlockchainCmd returns a new instance which can be used to issue
@ -986,7 +971,7 @@ type RescanBlockchainCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // for optional parameters will use the default value.
func NewRescanBlockchainCmd(startHeight *int32, stopHeight *int32) *RescanBlockchainCmd { func NewRescanBlockchainCmd(startHeight *int64, stopHeight *int64) *RescanBlockchainCmd {
return &RescanBlockchainCmd{ return &RescanBlockchainCmd{
StartHeight: startHeight, StartHeight: startHeight,
StopHeight: stopHeight, StopHeight: stopHeight,

View file

@ -287,12 +287,11 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("getaccountaddress", "acct") return btcjson.NewCmd("getaccountaddress", "acct")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewGetAccountAddressCmd(btcjson.String("acct")) return btcjson.NewGetAccountAddressCmd("acct")
}, },
marshalled: `{"jsonrpc":"1.0","method":"getaccountaddress","params":["acct"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getaccountaddress","params":["acct"],"id":1}`,
unmarshalled: &btcjson.GetAccountAddressCmd{ unmarshalled: &btcjson.GetAccountAddressCmd{
Account: btcjson.String("acct"), Account: "acct",
AddressType: btcjson.String("legacy"),
}, },
}, },
{ {
@ -301,12 +300,11 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("getaddressesbyaccount", "acct") return btcjson.NewCmd("getaddressesbyaccount", "acct")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewGetAddressesByAccountCmd(btcjson.String("acct")) return btcjson.NewGetAddressesByAccountCmd("acct")
}, },
marshalled: `{"jsonrpc":"1.0","method":"getaddressesbyaccount","params":["acct"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getaddressesbyaccount","params":["acct"],"id":1}`,
unmarshalled: &btcjson.GetAddressesByAccountCmd{ unmarshalled: &btcjson.GetAddressesByAccountCmd{
Account: btcjson.String("acct"), Account: "acct",
AddressType: btcjson.String("*"),
}, },
}, },
{ {
@ -332,9 +330,8 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":[],"id":1}`,
unmarshalled: &btcjson.GetBalanceCmd{ unmarshalled: &btcjson.GetBalanceCmd{
Account: btcjson.String("default"), Account: nil,
MinConf: btcjson.Int(1), MinConf: btcjson.Int(1),
AddressType: btcjson.String("*"),
}, },
}, },
{ {
@ -347,9 +344,8 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct"],"id":1}`,
unmarshalled: &btcjson.GetBalanceCmd{ unmarshalled: &btcjson.GetBalanceCmd{
Account: btcjson.String("acct"), Account: btcjson.String("acct"),
MinConf: btcjson.Int(1), MinConf: btcjson.Int(1),
AddressType: btcjson.String("*"),
}, },
}, },
{ {
@ -362,9 +358,8 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct",6],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct",6],"id":1}`,
unmarshalled: &btcjson.GetBalanceCmd{ unmarshalled: &btcjson.GetBalanceCmd{
Account: btcjson.String("acct"), Account: btcjson.String("acct"),
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("*"),
}, },
}, },
{ {
@ -388,8 +383,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":[],"id":1}`,
unmarshalled: &btcjson.GetNewAddressCmd{ unmarshalled: &btcjson.GetNewAddressCmd{
Account: btcjson.String("default"), Account: nil,
AddressType: btcjson.String("legacy"),
}, },
}, },
{ {
@ -402,8 +396,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":["acct"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":["acct"],"id":1}`,
unmarshalled: &btcjson.GetNewAddressCmd{ unmarshalled: &btcjson.GetNewAddressCmd{
Account: btcjson.String("acct"), Account: btcjson.String("acct"),
AddressType: btcjson.String("legacy"),
}, },
}, },
{ {
@ -416,8 +409,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":[],"id":1}`,
unmarshalled: &btcjson.GetRawChangeAddressCmd{ unmarshalled: &btcjson.GetRawChangeAddressCmd{
Account: btcjson.String("default"), Account: nil,
AddressType: btcjson.String("legacy"),
}, },
}, },
{ {
@ -430,8 +422,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":["acct"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":["acct"],"id":1}`,
unmarshalled: &btcjson.GetRawChangeAddressCmd{ unmarshalled: &btcjson.GetRawChangeAddressCmd{
Account: btcjson.String("acct"), Account: btcjson.String("acct"),
AddressType: btcjson.String("legacy"),
}, },
}, },
{ {
@ -440,11 +431,11 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("getreceivedbyaccount", "acct") return btcjson.NewCmd("getreceivedbyaccount", "acct")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("acct"), nil) return btcjson.NewGetReceivedByAccountCmd("acct", nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct"],"id":1}`,
unmarshalled: &btcjson.GetReceivedByAccountCmd{ unmarshalled: &btcjson.GetReceivedByAccountCmd{
Account: btcjson.String("acct"), Account: "acct",
MinConf: btcjson.Int(1), MinConf: btcjson.Int(1),
}, },
}, },
@ -454,11 +445,11 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("getreceivedbyaccount", "acct", 6) return btcjson.NewCmd("getreceivedbyaccount", "acct", 6)
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("acct"), btcjson.Int(6)) return btcjson.NewGetReceivedByAccountCmd("acct", btcjson.Int(6))
}, },
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct",6],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct",6],"id":1}`,
unmarshalled: &btcjson.GetReceivedByAccountCmd{ unmarshalled: &btcjson.GetReceivedByAccountCmd{
Account: btcjson.String("acct"), Account: "acct",
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
}, },
}, },
@ -610,8 +601,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[],"id":1}`,
unmarshalled: &btcjson.ListAccountsCmd{ unmarshalled: &btcjson.ListAccountsCmd{
MinConf: btcjson.Int(1), MinConf: btcjson.Int(1),
AddressType: btcjson.String("*"),
}, },
}, },
{ {
@ -624,8 +614,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[6],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[6],"id":1}`,
unmarshalled: &btcjson.ListAccountsCmd{ unmarshalled: &btcjson.ListAccountsCmd{
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("*"),
}, },
}, },
{ {
@ -855,7 +844,7 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":[],"id":1}`,
unmarshalled: &btcjson.ListTransactionsCmd{ unmarshalled: &btcjson.ListTransactionsCmd{
Account: btcjson.String("default"), Account: nil,
Count: btcjson.Int(10), Count: btcjson.Int(10),
From: btcjson.Int(0), From: btcjson.Int(0),
IncludeWatchOnly: btcjson.Bool(false), IncludeWatchOnly: btcjson.Bool(false),
@ -1013,7 +1002,7 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5) return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5)
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil, nil) return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`,
unmarshalled: &btcjson.SendFromCmd{ unmarshalled: &btcjson.SendFromCmd{
@ -1021,7 +1010,6 @@ func TestWalletSvrCmds(t *testing.T) {
ToAddress: "1Address", ToAddress: "1Address",
Amount: 0.5, Amount: 0.5,
MinConf: btcjson.Int(1), MinConf: btcjson.Int(1),
AddressType: btcjson.String("*"),
Comment: nil, Comment: nil,
CommentTo: nil, CommentTo: nil,
}, },
@ -1032,7 +1020,7 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6) return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6)
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil, nil) return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`,
unmarshalled: &btcjson.SendFromCmd{ unmarshalled: &btcjson.SendFromCmd{
@ -1040,7 +1028,6 @@ func TestWalletSvrCmds(t *testing.T) {
ToAddress: "1Address", ToAddress: "1Address",
Amount: 0.5, Amount: 0.5,
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("*"),
Comment: nil, Comment: nil,
CommentTo: nil, CommentTo: nil,
}, },
@ -1048,59 +1035,37 @@ func TestWalletSvrCmds(t *testing.T) {
{ {
name: "sendfrom optional2", name: "sendfrom optional2",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy") return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"), return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
nil, nil) btcjson.String("comment"), nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment"],"id":1}`,
unmarshalled: &btcjson.SendFromCmd{ unmarshalled: &btcjson.SendFromCmd{
FromAccount: "from", FromAccount: "from",
ToAddress: "1Address", ToAddress: "1Address",
Amount: 0.5, Amount: 0.5,
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("legacy"), Comment: btcjson.String("comment"),
Comment: nil,
CommentTo: nil, CommentTo: nil,
}, },
}, },
{ {
name: "sendfrom optional3", name: "sendfrom optional3",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy", "comment") return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment", "commentto")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"), return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
btcjson.String("comment"), nil)
},
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment"],"id":1}`,
unmarshalled: &btcjson.SendFromCmd{
FromAccount: "from",
ToAddress: "1Address",
Amount: 0.5,
MinConf: btcjson.Int(6),
AddressType: btcjson.String("legacy"),
Comment: btcjson.String("comment"),
CommentTo: nil,
},
},
{
name: "sendfrom optional4",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy", "comment", "commentto")
},
staticCmd: func() interface{} {
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
btcjson.String("comment"), btcjson.String("commentto")) btcjson.String("comment"), btcjson.String("commentto"))
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment","commentto"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment","commentto"],"id":1}`,
unmarshalled: &btcjson.SendFromCmd{ unmarshalled: &btcjson.SendFromCmd{
FromAccount: "from", FromAccount: "from",
ToAddress: "1Address", ToAddress: "1Address",
Amount: 0.5, Amount: 0.5,
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("legacy"),
Comment: btcjson.String("comment"), Comment: btcjson.String("comment"),
CommentTo: btcjson.String("commentto"), CommentTo: btcjson.String("commentto"),
}, },
@ -1112,14 +1077,13 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
amounts := map[string]float64{"1Address": 0.5} amounts := map[string]float64{"1Address": 0.5}
return btcjson.NewSendManyCmd("from", amounts, nil, nil, nil) return btcjson.NewSendManyCmd("from", amounts, nil, nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`,
unmarshalled: &btcjson.SendManyCmd{ unmarshalled: &btcjson.SendManyCmd{
FromAccount: "from", FromAccount: "from",
Amounts: map[string]float64{"1Address": 0.5}, Amounts: map[string]float64{"1Address": 0.5},
MinConf: btcjson.Int(1), MinConf: btcjson.Int(1),
AddressType: btcjson.String("*"),
Comment: nil, Comment: nil,
}, },
}, },
@ -1130,50 +1094,30 @@ func TestWalletSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
amounts := map[string]float64{"1Address": 0.5} amounts := map[string]float64{"1Address": 0.5}
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil, nil) return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`,
unmarshalled: &btcjson.SendManyCmd{ unmarshalled: &btcjson.SendManyCmd{
FromAccount: "from", FromAccount: "from",
Amounts: map[string]float64{"1Address": 0.5}, Amounts: map[string]float64{"1Address": 0.5},
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("*"),
Comment: nil, Comment: nil,
}, },
}, },
{ {
name: "sendmany optional2", name: "sendmany optional2",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "legacy") return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "comment")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
amounts := map[string]float64{"1Address": 0.5} amounts := map[string]float64{"1Address": 0.5}
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("legacy"), nil) return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("comment"))
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"legacy"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"comment"],"id":1}`,
unmarshalled: &btcjson.SendManyCmd{ unmarshalled: &btcjson.SendManyCmd{
FromAccount: "from", FromAccount: "from",
Amounts: map[string]float64{"1Address": 0.5}, Amounts: map[string]float64{"1Address": 0.5},
MinConf: btcjson.Int(6), MinConf: btcjson.Int(6),
AddressType: btcjson.String("legacy"),
Comment: nil,
},
},
{
name: "sendmany optional3",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "legacy", "comment")
},
staticCmd: func() interface{} {
amounts := map[string]float64{"1Address": 0.5}
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("legacy"), btcjson.String("comment"))
},
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"legacy","comment"],"id":1}`,
unmarshalled: &btcjson.SendManyCmd{
FromAccount: "from",
Amounts: map[string]float64{"1Address": 0.5},
MinConf: btcjson.Int(6),
AddressType: btcjson.String("legacy"),
Comment: btcjson.String("comment"), Comment: btcjson.String("comment"),
}, },
}, },
@ -1183,50 +1127,31 @@ func TestWalletSvrCmds(t *testing.T) {
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5) return btcjson.NewCmd("sendtoaddress", "1Address", 0.5)
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil, nil) return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`,
unmarshalled: &btcjson.SendToAddressCmd{ unmarshalled: &btcjson.SendToAddressCmd{
Address: "1Address", Address: "1Address",
Amount: 0.5, Amount: 0.5,
AddressType: btcjson.String("*"), Comment: nil,
Comment: nil, CommentTo: nil,
CommentTo: nil,
}, },
}, },
{ {
name: "sendtoaddress optional1", name: "sendtoaddress optional1",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy") return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto")
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), nil, nil) return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("comment"),
},
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy"],"id":1}`,
unmarshalled: &btcjson.SendToAddressCmd{
Address: "1Address",
Amount: 0.5,
AddressType: btcjson.String("legacy"),
Comment: nil,
CommentTo: nil,
},
},
{
name: "sendtoaddress optional2",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy", "comment", "commentto")
},
staticCmd: func() interface{} {
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), btcjson.String("comment"),
btcjson.String("commentto")) btcjson.String("commentto"))
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy","comment","commentto"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"comment","commentto"],"id":1}`,
unmarshalled: &btcjson.SendToAddressCmd{ unmarshalled: &btcjson.SendToAddressCmd{
Address: "1Address", Address: "1Address",
Amount: 0.5, Amount: 0.5,
AddressType: btcjson.String("legacy"), Comment: btcjson.String("comment"),
Comment: btcjson.String("comment"), CommentTo: btcjson.String("commentto"),
CommentTo: btcjson.String("commentto"),
}, },
}, },
{ {

View file

@ -174,7 +174,6 @@ type GetTransactionResult struct {
TimeReceived int64 `json:"timereceived"` TimeReceived int64 `json:"timereceived"`
Details []GetTransactionDetailsResult `json:"details"` Details []GetTransactionDetailsResult `json:"details"`
Hex string `json:"hex"` Hex string `json:"hex"`
Generated bool `json:"generated"`
} }
type ScanningOrFalse struct { type ScanningOrFalse struct {
@ -289,6 +288,7 @@ type ListReceivedByAccountResult struct {
// ListReceivedByAddressResult models the data from the listreceivedbyaddress // ListReceivedByAddressResult models the data from the listreceivedbyaddress
// command. // command.
type ListReceivedByAddressResult struct { type ListReceivedByAddressResult struct {
Account string `json:"account"`
Address string `json:"address"` Address string `json:"address"`
Amount float64 `json:"amount"` Amount float64 `json:"amount"`
Confirmations uint64 `json:"confirmations"` Confirmations uint64 `json:"confirmations"`
@ -319,8 +319,8 @@ type ListUnspentResult struct {
// RescanBlockchainResult models the data returned from the rescanblockchain command. // RescanBlockchainResult models the data returned from the rescanblockchain command.
type RescanBlockchainResult struct { type RescanBlockchainResult struct {
StartHeight int32 `json:"start_height"` StartHeight int64 `json:"start_height"`
StoptHeight int32 `json:"stop_height"` StoptHeight int64 `json:"stop_height"`
} }
// SignRawTransactionError models the data that contains script verification // SignRawTransactionError models the data that contains script verification

View file

@ -40,7 +40,7 @@ func NewExportWatchingWalletCmd(account *string, download *bool) *ExportWatching
// GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command. // GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command.
type GetUnconfirmedBalanceCmd struct { type GetUnconfirmedBalanceCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account *string
} }
// NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue // NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue
@ -58,7 +58,7 @@ func NewGetUnconfirmedBalanceCmd(account *string) *GetUnconfirmedBalanceCmd {
// command. // command.
type ListAddressTransactionsCmd struct { type ListAddressTransactionsCmd struct {
Addresses []string Addresses []string
Account *string `jsonrpcdefault:"\"default\""` Account *string
} }
// NewListAddressTransactionsCmd returns a new instance which can be used to // NewListAddressTransactionsCmd returns a new instance which can be used to
@ -75,7 +75,7 @@ func NewListAddressTransactionsCmd(addresses []string, account *string) *ListAdd
// ListAllTransactionsCmd defines the listalltransactions JSON-RPC command. // ListAllTransactionsCmd defines the listalltransactions JSON-RPC command.
type ListAllTransactionsCmd struct { type ListAllTransactionsCmd struct {
Account *string `jsonrpcdefault:"\"default\""` Account *string
} }
// NewListAllTransactionsCmd returns a new instance which can be used to issue a // NewListAllTransactionsCmd returns a new instance which can be used to issue a

View file

@ -71,7 +71,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
{ {
name: "exportwatchingwallet optional2", name: "exportwatchingwallet optional2",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("exportwatchingwallet", btcjson.String("acct"), true) return btcjson.NewCmd("exportwatchingwallet", "acct", true)
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"), return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"),
@ -93,7 +93,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`,
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{ unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
Account: btcjson.String("default"), Account: nil,
}, },
}, },
{ {
@ -120,7 +120,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`,
unmarshalled: &btcjson.ListAddressTransactionsCmd{ unmarshalled: &btcjson.ListAddressTransactionsCmd{
Addresses: []string{"1Address"}, Addresses: []string{"1Address"},
Account: btcjson.String("default"), Account: nil,
}, },
}, },
{ {
@ -148,7 +148,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
}, },
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`,
unmarshalled: &btcjson.ListAllTransactionsCmd{ unmarshalled: &btcjson.ListAllTransactionsCmd{
Account: btcjson.String("default"), Account: nil,
}, },
}, },
{ {

View file

@ -4,7 +4,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"sync"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -247,17 +249,17 @@ func (ct *ClaimTrie) AppendBlock(temporary bool) error {
names = append(names, expirations...) names = append(names, expirations...)
names = removeDuplicates(names) names = removeDuplicates(names)
for _, name := range names { nhns := ct.makeNameHashNext(names, false, nil)
for nhn := range nhns {
hash, next := ct.nodeManager.Hash(name) ct.merkleTrie.Update(nhn.Name, nhn.Hash, true)
ct.merkleTrie.Update(name, hash, true) if nhn.Next <= 0 {
if next <= 0 {
continue continue
} }
newName := normalization.NormalizeIfNecessary(name, next) newName := normalization.NormalizeIfNecessary(nhn.Name, nhn.Next)
updateNames = append(updateNames, newName) updateNames = append(updateNames, newName)
updateHeights = append(updateHeights, next) updateHeights = append(updateHeights, nhn.Next)
} }
if !temporary && len(updateNames) > 0 { if !temporary && len(updateNames) > 0 {
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights) err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
@ -354,29 +356,22 @@ func (ct *ClaimTrie) ResetHeight(height int32) error {
} }
func (ct *ClaimTrie) runFullTrieRebuild(names [][]byte, interrupt <-chan struct{}) { func (ct *ClaimTrie) runFullTrieRebuild(names [][]byte, interrupt <-chan struct{}) {
var nhns chan NameHashNext
if names == nil { if names == nil {
node.Log("Building the entire claim trie in RAM...") node.Log("Building the entire claim trie in RAM...")
ct.claimLogger = newClaimProgressLogger("Processed", node.GetLogger()) ct.claimLogger = newClaimProgressLogger("Processed", node.GetLogger())
nhns = ct.makeNameHashNext(nil, true, interrupt)
ct.nodeManager.IterateNames(func(name []byte) bool {
if interruptRequested(interrupt) {
return false
}
clone := make([]byte, len(name))
copy(clone, name)
hash, _ := ct.nodeManager.Hash(clone)
ct.merkleTrie.Update(clone, hash, false)
ct.claimLogger.LogName(name)
return true
})
} else { } else {
for _, name := range names { ct.claimLogger = nil
hash, _ := ct.nodeManager.Hash(name) nhns = ct.makeNameHashNext(names, false, interrupt)
ct.merkleTrie.Update(name, hash, false)
}
} }
for nhn := range nhns {
ct.merkleTrie.Update(nhn.Name, nhn.Hash, false)
if ct.claimLogger != nil {
ct.claimLogger.LogName(nhn.Name)
}
}
} }
// MerkleHash returns the Merkle Hash of the claimTrie. // MerkleHash returns the Merkle Hash of the claimTrie.
@ -442,6 +437,12 @@ func (ct *ClaimTrie) FlushToDisk() {
} }
} }
type NameHashNext struct {
Name []byte
Hash *chainhash.Hash
Next int32
}
func interruptRequested(interrupted <-chan struct{}) bool { func interruptRequested(interrupted <-chan struct{}) bool {
select { select {
case <-interrupted: // should never block on nil case <-interrupted: // should never block on nil
@ -451,3 +452,53 @@ func interruptRequested(interrupted <-chan struct{}) bool {
return false return false
} }
func (ct *ClaimTrie) makeNameHashNext(names [][]byte, all bool, interrupt <-chan struct{}) chan NameHashNext {
inputs := make(chan []byte, 512)
outputs := make(chan NameHashNext, 512)
var wg sync.WaitGroup
hashComputationWorker := func() {
for name := range inputs {
hash, next := ct.nodeManager.Hash(name)
outputs <- NameHashNext{name, hash, next}
}
wg.Done()
}
threads := int(0.8 * float32(runtime.GOMAXPROCS(0)))
if threads < 1 {
threads = 1
}
for threads > 0 {
threads--
wg.Add(1)
go hashComputationWorker()
}
go func() {
if all {
ct.nodeManager.IterateNames(func(name []byte) bool {
if interruptRequested(interrupt) {
return false
}
clone := make([]byte, len(name))
copy(clone, name) // iteration name buffer is reused on future loops
inputs <- clone
return true
})
} else {
for _, name := range names {
if interruptRequested(interrupt) {
break
}
inputs <- name
}
}
close(inputs)
}()
go func() {
wg.Wait()
close(outputs)
}()
return outputs
}

View file

@ -1,85 +0,0 @@
package node
import (
"container/list"
"github.com/lbryio/lbcd/claimtrie/change"
)
type cacheLeaf struct {
node *Node
element *list.Element
changes []change.Change
height int32
}
type Cache struct {
nodes map[string]*cacheLeaf
order *list.List
limit int
}
func (nc *Cache) insert(name []byte, n *Node, height int32) {
key := string(name)
existing := nc.nodes[key]
if existing != nil {
existing.node = n
existing.height = height
existing.changes = nil
nc.order.MoveToFront(existing.element)
return
}
for nc.order.Len() >= nc.limit {
// TODO: maybe ensure that we don't remove nodes that have a lot of changes?
delete(nc.nodes, nc.order.Back().Value.(string))
nc.order.Remove(nc.order.Back())
}
element := nc.order.PushFront(key)
nc.nodes[key] = &cacheLeaf{node: n, element: element, height: height}
}
func (nc *Cache) fetch(name []byte, height int32) (*Node, []change.Change, int32) {
key := string(name)
existing := nc.nodes[key]
if existing != nil && existing.height <= height {
nc.order.MoveToFront(existing.element)
return existing.node, existing.changes, existing.height
}
return nil, nil, -1
}
func (nc *Cache) addChanges(changes []change.Change, height int32) {
for _, c := range changes {
key := string(c.Name)
existing := nc.nodes[key]
if existing != nil && existing.height <= height {
existing.changes = append(existing.changes, c)
}
}
}
func (nc *Cache) drop(names [][]byte) {
for _, name := range names {
key := string(name)
existing := nc.nodes[key]
if existing != nil {
// we can't roll it backwards because we don't know its previous height value; just toast it
delete(nc.nodes, key)
nc.order.Remove(existing.element)
}
}
}
func (nc *Cache) clear() {
nc.nodes = map[string]*cacheLeaf{}
nc.order = list.New()
// we'll let the GC sort out the remains...
}
func NewCache(limit int) *Cache {
return &Cache{limit: limit, nodes: map[string]*cacheLeaf{}, order: list.New()}
}

View file

@ -21,7 +21,6 @@ type Manager interface {
IterateNames(predicate func(name []byte) bool) IterateNames(predicate func(name []byte) bool)
Hash(name []byte) (*chainhash.Hash, int32) Hash(name []byte) (*chainhash.Hash, int32)
Flush() error Flush() error
ClearCache()
} }
type BaseManager struct { type BaseManager struct {
@ -31,62 +30,31 @@ type BaseManager struct {
changes []change.Change changes []change.Change
tempChanges map[string][]change.Change tempChanges map[string][]change.Change
cache *Cache
} }
func NewBaseManager(repo Repo) (*BaseManager, error) { func NewBaseManager(repo Repo) (*BaseManager, error) {
nm := &BaseManager{ nm := &BaseManager{
repo: repo, repo: repo,
cache: NewCache(10000), // TODO: how many should we cache?
} }
return nm, nil return nm, nil
} }
func (nm *BaseManager) ClearCache() {
nm.cache.clear()
}
func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) { func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) {
n, changes, oldHeight := nm.cache.fetch(name, height) changes, err := nm.repo.LoadChanges(name)
if n == nil { if err != nil {
changes, err := nm.repo.LoadChanges(name) return nil, errors.Wrap(err, "in load changes")
if err != nil { }
return nil, errors.Wrap(err, "in load changes")
}
if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block
changes = append(changes, nm.tempChanges[string(name)]...) changes = append(changes, nm.tempChanges[string(name)]...)
} }
n, err = nm.newNodeFromChanges(changes, height) n, err := nm.newNodeFromChanges(changes, height)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "in new node") return nil, errors.Wrap(err, "in new node")
}
// TODO: how can we tell what needs to be cached?
if nm.tempChanges == nil && height == nm.height && n != nil && (len(changes) > 4 || len(name) < 12) {
nm.cache.insert(name, n, height)
}
} else {
if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block
changes = append(changes, nm.tempChanges[string(name)]...)
n = n.Clone()
} else if height != nm.height {
n = n.Clone()
}
updated, err := nm.updateFromChanges(n, changes, height)
if err != nil {
return nil, errors.Wrap(err, "in update from changes")
}
if !updated {
n.AdjustTo(oldHeight, height, name)
}
if nm.tempChanges == nil && height == nm.height {
nm.cache.insert(name, n, height)
}
} }
return n, nil return n, nil
@ -98,13 +66,17 @@ func (nm *BaseManager) node(name []byte) (*Node, error) {
return nm.NodeAt(nm.height, name) return nm.NodeAt(nm.height, name)
} }
func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, height int32) (bool, error) { // newNodeFromChanges returns a new Node constructed from the changes.
// The changes must preserve their order received.
func (nm *BaseManager) newNodeFromChanges(changes []change.Change, height int32) (*Node, error) {
count := len(changes) if len(changes) == 0 {
if count == 0 { return nil, nil
return false, nil
} }
n := New()
previous := changes[0].Height previous := changes[0].Height
count := len(changes)
for i, chg := range changes { for i, chg := range changes {
if chg.Height < previous { if chg.Height < previous {
@ -123,37 +95,15 @@ func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, heigh
delay := nm.getDelayForName(n, chg) delay := nm.getDelayForName(n, chg)
err := n.ApplyChange(chg, delay) err := n.ApplyChange(chg, delay)
if err != nil { if err != nil {
return false, errors.Wrap(err, "in apply change") return nil, errors.Wrap(err, "in apply change")
} }
} }
if count <= 0 { if count <= 0 {
// we applied no changes, which means we shouldn't exist if we had all the changes
// or might mean nothing significant if we are applying a partial changeset
return false, nil
}
lastChange := changes[count-1]
n.AdjustTo(lastChange.Height, height, lastChange.Name)
return true, nil
}
// newNodeFromChanges returns a new Node constructed from the changes.
// The changes must preserve their order received.
func (nm *BaseManager) newNodeFromChanges(changes []change.Change, height int32) (*Node, error) {
if len(changes) == 0 {
return nil, nil return nil, nil
} }
lastChange := changes[count-1]
n := New() return n.AdjustTo(lastChange.Height, height, lastChange.Name), nil
updated, err := nm.updateFromChanges(n, changes, height)
if err != nil {
return nil, errors.Wrap(err, "in update from changes")
}
if updated {
return n, nil
}
return nil, nil
} }
func (nm *BaseManager) AppendChange(chg change.Change) { func (nm *BaseManager) AppendChange(chg change.Change) {
@ -270,7 +220,6 @@ func (nm *BaseManager) IncrementHeightTo(height int32, temporary bool) ([][]byte
} }
if !temporary { if !temporary {
nm.cache.addChanges(nm.changes, height)
if err := nm.repo.AppendChanges(nm.changes); err != nil { // destroys names if err := nm.repo.AppendChanges(nm.changes); err != nil { // destroys names
return nil, errors.Wrap(err, "in append changes") return nil, errors.Wrap(err, "in append changes")
} }
@ -306,8 +255,6 @@ func (nm *BaseManager) DecrementHeightTo(affectedNames [][]byte, height int32) (
return affectedNames, errors.Wrap(err, "in drop changes") return affectedNames, errors.Wrap(err, "in drop changes")
} }
} }
nm.cache.drop(affectedNames)
} }
nm.height = height nm.height = height

View file

@ -110,7 +110,7 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error {
} }
// AdjustTo activates claims and computes takeovers until it reaches the specified height. // AdjustTo activates claims and computes takeovers until it reaches the specified height.
func (n *Node) AdjustTo(height, maxHeight int32, name []byte) { func (n *Node) AdjustTo(height, maxHeight int32, name []byte) *Node {
changed := n.handleExpiredAndActivated(height) > 0 changed := n.handleExpiredAndActivated(height) > 0
n.updateTakeoverHeight(height, name, changed) n.updateTakeoverHeight(height, name, changed)
if maxHeight > height { if maxHeight > height {
@ -120,6 +120,7 @@ func (n *Node) AdjustTo(height, maxHeight int32, name []byte) {
height = h height = h
} }
} }
return n
} }
func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool) { func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool) {
@ -339,28 +340,3 @@ func (n *Node) SortClaimsByBid() {
return OutPointLess(n.Claims[j].OutPoint, n.Claims[i].OutPoint) return OutPointLess(n.Claims[j].OutPoint, n.Claims[i].OutPoint)
}) })
} }
func (n *Node) Clone() *Node {
clone := New()
if n.SupportSums != nil {
clone.SupportSums = map[string]int64{}
for key, value := range n.SupportSums {
clone.SupportSums[key] = value
}
}
clone.Supports = make(ClaimList, len(n.Supports))
for i, support := range n.Supports {
clone.Supports[i] = &Claim{}
*clone.Supports[i] = *support
}
clone.Claims = make(ClaimList, len(n.Claims))
for i, claim := range n.Claims {
clone.Claims[i] = &Claim{}
*clone.Claims[i] = *claim
}
clone.TakenOverAt = n.TakenOverAt
if n.BestClaim != nil {
clone.BestClaim = clone.Claims.find(byID(n.BestClaim.ClaimID))
}
return clone
}

View file

@ -34,7 +34,6 @@ func (nm *NormalizingManager) IncrementHeightTo(height int32, temporary bool) ([
func (nm *NormalizingManager) DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error) { func (nm *NormalizingManager) DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error) {
if nm.normalizedAt > height { if nm.normalizedAt > height {
nm.normalizedAt = -1 nm.normalizedAt = -1
nm.ClearCache()
} }
return nm.Manager.DecrementHeightTo(affectedNames, height) return nm.Manager.DecrementHeightTo(affectedNames, height)
} }
@ -111,7 +110,5 @@ func (nm *NormalizingManager) addNormalizationForkChangesIfNecessary(height int3
return true return true
} }
nm.Manager.ClearCache()
nm.Manager.IterateNames(predicate) nm.Manager.IterateNames(predicate)
} }

View file

@ -111,8 +111,6 @@ type config struct {
SigNet bool `long:"signet" description:"Connect to signet (default RPC server: localhost:49245)"` SigNet bool `long:"signet" description:"Connect to signet (default RPC server: localhost:49245)"`
Wallet bool `long:"wallet" description:"Connect to wallet RPC server instead (default: localhost:9244, testnet: localhost:19244, regtest: localhost:29244)"` Wallet bool `long:"wallet" description:"Connect to wallet RPC server instead (default: localhost:9244, testnet: localhost:19244, regtest: localhost:29244)"`
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
Timed bool `short:"t" long:"timed" description:"Display RPC response time"`
Quiet bool `short:"q" long:"quiet" description:"Do not output results to stdout"`
} }
// normalizeAddress returns addr with the passed default port appended if // normalizeAddress returns addr with the passed default port appended if

View file

@ -9,7 +9,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/lbryio/lbcd/btcjson" "github.com/lbryio/lbcd/btcjson"
) )
@ -134,8 +133,6 @@ func main() {
os.Exit(1) os.Exit(1)
} }
started := time.Now()
// Send the JSON-RPC request to the server using the user-specified // Send the JSON-RPC request to the server using the user-specified
// connection configuration. // connection configuration.
result, err := sendPostRequest(marshalledJSON, cfg) result, err := sendPostRequest(marshalledJSON, cfg)
@ -144,16 +141,6 @@ func main() {
os.Exit(1) os.Exit(1)
} }
if cfg.Timed {
elapsed := time.Since(started)
defer fmt.Fprintf(os.Stderr, "%s\n", elapsed)
}
var output io.Writer = os.Stdout
if cfg.Quiet {
output = io.Discard
}
// Choose how to display the result based on its type. // Choose how to display the result based on its type.
strResult := string(result) strResult := string(result)
if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") { if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") {
@ -163,7 +150,7 @@ func main() {
err) err)
os.Exit(1) os.Exit(1)
} }
fmt.Fprintln(output, dst.String()) fmt.Println(dst.String())
} else if strings.HasPrefix(strResult, `"`) { } else if strings.HasPrefix(strResult, `"`) {
var str string var str string
@ -172,9 +159,9 @@ func main() {
err) err)
os.Exit(1) os.Exit(1)
} }
fmt.Fprintln(output, str) fmt.Println(str)
} else if strResult != "null" { } else if strResult != "null" {
fmt.Fprintln(output, strResult) fmt.Println(strResult)
} }
} }

View file

@ -1,42 +0,0 @@
#! /bin/bash
read -r -d '' help << EOM
$0 - helper script for displaying miner of a mined block.
Options:
-h Display this message.
--height Specify blockheight.
--hash Specify blockhash.
EOM
while getopts ":h-:" optchar; do
case "${optchar}" in
-)
case "${OPTARG}" in
hash)
blockhash="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
;;
height)
blockheight="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
blockhash=$(lbcctl getblockhash ${blockheight})
;;
*) echo "Unknown long option --${OPTARG}" >&2; exit -2 ;;
esac
;;
h) printf "${help}\n\n"; exit 2;;
*) echo "Unknown option -${OPTARG}" >&2; exit -2;;
esac
done
block=$(lbcctl getblock $blockhash)
blockheight=$(lbcctl getblock $blockhash | jq -r .height)
coinbase_txid=$(echo ${block} | jq -r '.tx[0]')
coinbase_raw=$(lbcctl getrawtransaction ${coinbase_txid} 1)
coinbase=$(echo ${coinbase_raw} | jq '.vin[0].coinbase')
miner=$(echo ${coinbase} | grep -o '2f.*2f' | xxd -r -p | strings)
echo ${blockheight}: ${blockhash}: ${miner}

View file

@ -13,17 +13,12 @@ import (
"fmt" "fmt"
"os" "os"
"runtime/debug" "runtime/debug"
"sort"
"testing" "testing"
"time"
"github.com/lbryio/lbcd/chaincfg" "github.com/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/chaincfg/chainhash" "github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/integration/rpctest" "github.com/lbryio/lbcd/integration/rpctest"
"github.com/lbryio/lbcd/rpcclient" "github.com/lbryio/lbcd/rpcclient"
"github.com/lbryio/lbcd/txscript"
"github.com/lbryio/lbcd/wire"
"github.com/lbryio/lbcutil"
) )
func testGetBestBlock(r *rpctest.Harness, t *testing.T) { func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
@ -138,278 +133,13 @@ func testBulkClient(r *rpctest.Harness, t *testing.T) {
t.Fatalf("expected hash %s to be in generated hash list", blockHash) t.Fatalf("expected hash %s to be in generated hash list", blockHash)
} }
} }
}
func testGetBlockStats(r *rpctest.Harness, t *testing.T) {
t.Parallel()
baseFeeRate := int64(10)
txValue := int64(50000000)
txQuantity := 10
txs := make([]*lbcutil.Tx, txQuantity)
fees := make([]int64, txQuantity)
sizes := make([]int64, txQuantity)
feeRates := make([]int64, txQuantity)
var outputCount int
// Generate test sample.
for i := 0; i < txQuantity; i++ {
address, err := r.NewAddress()
if err != nil {
t.Fatalf("Unable to generate address: %v", err)
}
pkScript, err := txscript.PayToAddrScript(address)
if err != nil {
t.Fatalf("Unable to generate PKScript: %v", err)
}
// This feerate is not the actual feerate. See comment below.
feeRate := baseFeeRate * int64(i)
tx, err := r.CreateTransaction([]*wire.TxOut{wire.NewTxOut(txValue, pkScript)}, lbcutil.Amount(feeRate), true)
if err != nil {
t.Fatalf("Unable to generate segwit transaction: %v", err)
}
txs[i] = lbcutil.NewTx(tx)
sizes[i] = int64(tx.SerializeSize())
// memWallet.fundTx makes some assumptions when calculating fees.
// For instance, it assumes the signature script has exactly 108 bytes
// and it does not account for the size of the change output.
// This needs to be taken into account when getting the true feerate.
scriptSigOffset := 108 - len(tx.TxIn[0].SignatureScript)
changeOutputSize := tx.TxOut[len(tx.TxOut)-1].SerializeSize()
fees[i] = (sizes[i] + int64(scriptSigOffset) - int64(changeOutputSize)) * feeRate
feeRates[i] = fees[i] / sizes[i]
outputCount += len(tx.TxOut)
}
stats := func(slice []int64) (int64, int64, int64, int64, int64) {
var total, average, min, max, median int64
min = slice[0]
length := len(slice)
for _, item := range slice {
if min > item {
min = item
}
if max < item {
max = item
}
total += item
}
average = total / int64(length)
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
if length == 0 {
median = 0
} else if length%2 == 0 {
median = (slice[length/2-1] + slice[length/2]) / 2
} else {
median = slice[length/2]
}
return total, average, min, max, median
}
totalFee, avgFee, minFee, maxFee, medianFee := stats(fees)
totalSize, avgSize, minSize, maxSize, medianSize := stats(sizes)
_, avgFeeRate, minFeeRate, maxFeeRate, _ := stats(feeRates)
tests := []struct {
name string
txs []*lbcutil.Tx
stats []string
expectedResults map[string]interface{}
}{
{
name: "empty block",
txs: []*lbcutil.Tx{},
stats: []string{},
expectedResults: map[string]interface{}{
"avgfee": int64(0),
"avgfeerate": int64(0),
"avgtxsize": int64(0),
"feerate_percentiles": []int64{0, 0, 0, 0, 0},
"ins": int64(0),
"maxfee": int64(0),
"maxfeerate": int64(0),
"maxtxsize": int64(0),
"medianfee": int64(0),
"mediantxsize": int64(0),
"minfee": int64(0),
"mintxsize": int64(0),
"outs": int64(1),
"swtotal_size": int64(0),
"swtotal_weight": int64(0),
"swtxs": int64(0),
"total_out": int64(0),
"total_size": int64(0),
"total_weight": int64(0),
"txs": int64(1),
"utxo_increase": int64(1),
},
},
{
name: "block with 10 transactions + coinbase",
txs: txs,
stats: []string{"avgfee", "avgfeerate", "avgtxsize", "feerate_percentiles",
"ins", "maxfee", "maxfeerate", "maxtxsize", "medianfee", "mediantxsize",
"minfee", "minfeerate", "mintxsize", "outs", "subsidy", "swtxs",
"total_size", "total_weight", "totalfee", "txs", "utxo_increase"},
expectedResults: map[string]interface{}{
"avgfee": avgFee,
"avgfeerate": avgFeeRate,
"avgtxsize": avgSize,
"feerate_percentiles": []int64{feeRates[0], feeRates[2],
feeRates[4], feeRates[7], feeRates[8]},
"ins": int64(txQuantity),
"maxfee": maxFee,
"maxfeerate": maxFeeRate,
"maxtxsize": maxSize,
"medianfee": medianFee,
"mediantxsize": medianSize,
"minfee": minFee,
"minfeerate": minFeeRate,
"mintxsize": minSize,
"outs": int64(outputCount + 1), // Coinbase output also counts.
"subsidy": int64(100000000),
"swtotal_weight": nil, // This stat was not selected, so it should be nil.
"swtxs": int64(0),
"total_size": totalSize,
"total_weight": totalSize * 4,
"totalfee": totalFee,
"txs": int64(txQuantity + 1), // Coinbase transaction also counts.
"utxo_increase": int64(outputCount + 1 - txQuantity),
"utxo_size_inc": nil,
},
},
}
for _, test := range tests {
// Submit a new block with the provided transactions.
block, err := r.GenerateAndSubmitBlock(test.txs, -1, time.Time{})
if err != nil {
t.Fatalf("Unable to generate block: %v from test %s", err, test.name)
}
blockStats, err := r.GetBlockStats(block.Hash(), &test.stats)
if err != nil {
t.Fatalf("Call to `getblockstats` on test %s failed: %v", test.name, err)
}
if blockStats.Height != (*int64)(nil) && *blockStats.Height != int64(block.Height()) {
t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, "height", block.Height(), *blockStats.Height)
}
for stat, value := range test.expectedResults {
var result interface{}
switch stat {
case "avgfee":
result = blockStats.AverageFee
case "avgfeerate":
result = blockStats.AverageFeeRate
case "avgtxsize":
result = blockStats.AverageTxSize
case "feerate_percentiles":
result = blockStats.FeeratePercentiles
case "blockhash":
result = blockStats.Hash
case "height":
result = blockStats.Height
case "ins":
result = blockStats.Ins
case "maxfee":
result = blockStats.MaxFee
case "maxfeerate":
result = blockStats.MaxFeeRate
case "maxtxsize":
result = blockStats.MaxTxSize
case "medianfee":
result = blockStats.MedianFee
case "mediantime":
result = blockStats.MedianTime
case "mediantxsize":
result = blockStats.MedianTxSize
case "minfee":
result = blockStats.MinFee
case "minfeerate":
result = blockStats.MinFeeRate
case "mintxsize":
result = blockStats.MinTxSize
case "outs":
result = blockStats.Outs
case "swtotal_size":
result = blockStats.SegWitTotalSize
case "swtotal_weight":
result = blockStats.SegWitTotalWeight
case "swtxs":
result = blockStats.SegWitTxs
case "subsidy":
result = blockStats.Subsidy
case "time":
result = blockStats.Time
case "total_out":
result = blockStats.TotalOut
case "total_size":
result = blockStats.TotalSize
case "total_weight":
result = blockStats.TotalWeight
case "totalfee":
result = blockStats.TotalFee
case "txs":
result = blockStats.Txs
case "utxo_increase":
result = blockStats.UTXOIncrease
case "utxo_size_inc":
result = blockStats.UTXOSizeIncrease
}
var equality bool
// Check for nil equality.
if value == nil && result == (*int64)(nil) {
equality = true
break
} else if result == nil || value == nil {
equality = false
}
var resultValue interface{}
switch v := value.(type) {
case int64:
resultValue = *result.(*int64)
equality = v == resultValue
case string:
resultValue = *result.(*string)
equality = v == resultValue
case []int64:
resultValue = *result.(*[]int64)
resultSlice := resultValue.([]int64)
equality = true
for i, item := range resultSlice {
if item != v[i] {
equality = false
break
}
}
}
if !equality {
if result != nil {
t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, stat, value, resultValue)
} else {
t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, stat, value, "<nil>")
}
}
}
}
} }
var rpcTestCases = []rpctest.HarnessTestCase{ var rpcTestCases = []rpctest.HarnessTestCase{
testGetBestBlock, testGetBestBlock,
testGetBlockCount, testGetBlockCount,
testGetBlockHash, testGetBlockHash,
testGetBlockStats,
testBulkClient, testBulkClient,
} }
@ -421,8 +151,7 @@ func TestMain(m *testing.M) {
// In order to properly test scenarios on as if we were on mainnet, // In order to properly test scenarios on as if we were on mainnet,
// ensure that non-standard transactions aren't accepted into the // ensure that non-standard transactions aren't accepted into the
// mempool or relayed. // mempool or relayed.
// Enable transaction index to be able to fully test GetBlockStats btcdCfg := []string{"--rejectnonstd"}
btcdCfg := []string{"--rejectnonstd", "--txindex"}
primaryHarness, err = rpctest.New( primaryHarness, err = rpctest.New(
&chaincfg.SimNetParams, nil, btcdCfg, "", &chaincfg.SimNetParams, nil, btcdCfg, "",
) )

View file

@ -16,7 +16,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/lbryio/lbcd/btcjson"
"github.com/lbryio/lbcd/chaincfg" "github.com/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/chaincfg/chainhash" "github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/rpcclient" "github.com/lbryio/lbcd/rpcclient"
@ -513,18 +512,6 @@ func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
return newBlock, nil return newBlock, nil
} }
// GetBlockStats returns block statistics. First argument specifies height or
// hash of the target block. Second argument allows to select certain stats to
// return. If second argument is empty, all stats are returned.
func (h *Harness) GetBlockStats(hashOrHeight interface{}, stats *[]string) (
*btcjson.GetBlockStatsResult, error) {
h.Lock()
defer h.Unlock()
return h.Client.GetBlockStats(hashOrHeight, stats)
}
// generateListeningAddresses returns two strings representing listening // generateListeningAddresses returns two strings representing listening
// addresses designated for the current rpc test. If there haven't been any // addresses designated for the current rpc test. If there haven't been any
// test instances created, the default ports are used. Otherwise, in order to // test instances created, the default ports are used. Otherwise, in order to

View file

@ -1,21 +1,15 @@
# lbcdbloknotify # lbcd Websockets Example
This bridge program subscribes to lbcd's notifications over websockets using the rpcclient package. This example shows how to use the rpcclient package to connect to a btcd RPC
Users can specify supported actions upon receiving this notifications. server using TLS-secured websockets, register for block connected and block
disconnected notifications, and get the current block count.
## Building(or Running) the Program ## Running the Example
Clone the lbcd package: The first step is to clone the lbcd package:
```bash ```bash
$ git clone github.com/lbryio/lbcd $ git clone github.com/lbryio/lbcd
$ cd lbcd/rpcclient/examples
# build the program
$ go build .
# or directly run it (build implicitly behind the scene)
$ go run .
``` ```
Display available options: Display available options:
@ -35,31 +29,18 @@ $ go run . -h
Stratum server (default "lbrypool.net:3334") Stratum server (default "lbrypool.net:3334")
-stratumpass string -stratumpass string
Stratum server password (default "password") Stratum server password (default "password")
-quiet
Do not print periodic logs
``` ```
Running the program: Start the program:
```bash ```bash
# Send stratum mining.update_block mesage upon receving block connected notifiations. $ go run . -stratumpass <STRATUM PASSWD> -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD>
$ go run . -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD> --notls -stratum <STRATUM SERVER> -stratumpass <STRATUM PASSWD>
2022/01/10 23:16:21 Current block count: 1093112 2022/01/10 23:16:21 NotifyBlocks: Registration Complete
2022/01/10 23:16:21 Block count: 1093112
... ...
# Execute a custome command (with blockhash) upon receving block connected notifiations.
$ go run . -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD> --notls -run "echo %s"
``` ```
## Notes
* Stratum TCP connection is persisted with auto-reconnect. (retry backoff increases from 1s to 60s maximum)
* Stratum update_block jobs on previous notifications are canceled when a new notification arrives.
Usually, the jobs are so short and completed immediately. However, if the Stratum connection is broken, this
prevents the bridge from accumulating stale jobs.
## License ## License
This example is licensed under the [copyfree](http://copyfree.org) ISC License. This example is licensed under the [copyfree](http://copyfree.org) ISC License.

View file

@ -1,20 +0,0 @@
package main
import (
"github.com/lbryio/lbcd/wire"
"github.com/lbryio/lbcutil"
)
type eventBlockConected struct {
height int32
header *wire.BlockHeader
txns []*lbcutil.Tx
}
type adapter struct {
*bridge
}
func (a *adapter) onFilteredBlockConnected(height int32, header *wire.BlockHeader, txns []*lbcutil.Tx) {
a.eventCh <- &eventBlockConected{height, header, txns}
}

View file

@ -1,172 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"net"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"time"
)
type bridge struct {
ctx context.Context
prevJobContext context.Context
prevJobCancel context.CancelFunc
eventCh chan interface{}
errorc chan error
wg sync.WaitGroup
stratum *stratumClient
customCmd string
}
func newBridge(stratumServer, stratumPass, coinid string) *bridge {
s := &bridge{
ctx: context.Background(),
eventCh: make(chan interface{}),
errorc: make(chan error),
}
if len(stratumServer) > 0 {
s.stratum = newStratumClient(stratumServer, stratumPass, coinid)
}
return s
}
func (b *bridge) start() {
if b.stratum != nil {
backoff := time.Second
for {
err := b.stratum.dial()
if err == nil {
break
}
log.Printf("WARN: stratum.dial() error: %s, retry in %s", err, backoff)
time.Sleep(backoff)
if backoff < 60*time.Second {
backoff += time.Second
}
}
}
for e := range b.eventCh {
switch e := e.(type) {
case *eventBlockConected:
b.handleFilteredBlockConnected(e)
default:
b.errorc <- fmt.Errorf("unknown event type: %T", e)
return
}
}
}
func (b *bridge) handleFilteredBlockConnected(e *eventBlockConected) {
if !*quiet {
log.Printf("Block connected: %s (%d) %v", e.header.BlockHash(), e.height, e.header.Timestamp)
}
hash := e.header.BlockHash().String()
height := e.height
// Cancel jobs on previous block. It's safe if they are already done.
if b.prevJobContext != nil {
select {
case <-b.prevJobContext.Done():
log.Printf("prev one canceled")
default:
b.prevJobCancel()
}
}
// Wait until all previous jobs are done or canceled.
b.wg.Wait()
// Create and save cancelable subcontext for new jobs.
ctx, cancel := context.WithCancel(b.ctx)
b.prevJobContext, b.prevJobCancel = ctx, cancel
if len(b.customCmd) > 0 {
go b.execCustomCommand(ctx, hash, height)
}
// Send stratum update block message
if b.stratum != nil {
go b.stratumUpdateBlock(ctx, hash, height)
}
}
func (s *bridge) stratumUpdateBlock(ctx context.Context, hash string, height int32) {
s.wg.Add(1)
defer s.wg.Done()
backoff := time.Second
retry := func(err error) {
if backoff < 60*time.Second {
backoff += time.Second
}
log.Printf("WARN: stratum.send() on block %d error: %s", height, err)
time.Sleep(backoff)
s.stratum.dial()
}
msg := stratumUpdateBlockMsg(*stratumPass, *coinid, hash)
for {
switch err := s.stratum.send(ctx, msg); {
case err == nil:
return
case errors.Is(err, context.Canceled):
log.Printf("INFO: stratum.send() on block %d: %s.", height, err)
return
case errors.Is(err, syscall.EPIPE):
errClose := s.stratum.conn.Close()
if errClose != nil {
log.Printf("WARN: stratum.conn.Close() on block %d: %s.", height, errClose)
}
retry(err)
case errors.Is(err, net.ErrClosed):
retry(err)
default:
retry(err)
}
}
}
func (s *bridge) execCustomCommand(ctx context.Context, hash string, height int32) {
s.wg.Add(1)
defer s.wg.Done()
cmd := strings.ReplaceAll(s.customCmd, "%s", hash)
err := doExecCustomCommand(ctx, cmd)
if err != nil {
log.Printf("ERROR: execCustomCommand on block %s(%d): %s", hash, height, err)
}
}
func doExecCustomCommand(ctx context.Context, cmd string) error {
strs := strings.Split(cmd, " ")
path, err := exec.LookPath(strs[0])
if errors.Is(err, exec.ErrDot) {
err = nil
}
if err != nil {
return err
}
c := exec.CommandContext(ctx, path, strs[1:]...)
c.Stdout = os.Stdout
return c.Run()
}

View file

@ -1,53 +0,0 @@
package main
import (
"io/ioutil"
"log"
"path/filepath"
"github.com/lbryio/lbcd/rpcclient"
)
func newLbcdClient(server, user, pass string, notls bool, adpt adapter) *rpcclient.Client {
ntfnHandlers := rpcclient.NotificationHandlers{
OnFilteredBlockConnected: adpt.onFilteredBlockConnected,
}
// Config lbcd RPC client with websockets.
connCfg := &rpcclient.ConnConfig{
Host: server,
Endpoint: "ws",
User: user,
Pass: pass,
DisableTLS: true,
}
if !notls {
cert, err := ioutil.ReadFile(filepath.Join(lbcdHomeDir, "rpc.cert"))
if err != nil {
log.Fatalf("can't read lbcd certificate: %s", err)
}
connCfg.Certificates = cert
connCfg.DisableTLS = false
}
client, err := rpcclient.New(connCfg, &ntfnHandlers)
if err != nil {
log.Fatalf("can't create rpc client: %s", err)
}
// Register for block connect and disconnect notifications.
if err = client.NotifyBlocks(); err != nil {
log.Fatalf("can't register block notification: %s", err)
}
// Get the current block count.
blockCount, err := client.GetBlockCount()
if err != nil {
log.Fatalf("can't get block count: %s", err)
}
log.Printf("Current block count: %d", blockCount)
return client
}

View file

@ -1,63 +1,133 @@
// Copyright (c) 2014-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main package main
import ( import (
"errors"
"flag" "flag"
"fmt"
"io/ioutil"
"log" "log"
"net"
"os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/lbryio/lbcd/rpcclient"
"github.com/lbryio/lbcd/wire"
"github.com/lbryio/lbcutil" "github.com/lbryio/lbcutil"
) )
var ( var (
lbcdHomeDir = lbcutil.AppDataDir("lbcd", false) coinid = flag.String("coinid", "1425", "Coin ID")
defaultCert = filepath.Join(lbcdHomeDir, "rpc.cert") stratum = flag.String("stratum", "", "Stratum server")
) stratumPass = flag.String("stratumpass", "", "Stratum server password")
var ( rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server")
coinid = flag.String("coinid", "1425", "Coin ID") rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username")
stratumServer = flag.String("stratum", "", "Stratum server") rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password")
stratumPass = flag.String("stratumpass", "", "Stratum server password") notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled")
rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server") run = flag.String("run", "", "Run custom shell command")
rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username")
rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password")
rpccert = flag.String("rpccert", defaultCert, "LBCD RPC certificate")
notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled")
run = flag.String("run", "", "Run custom shell command")
quiet = flag.Bool("quiet", false, "Do not print logs")
) )
func onFilteredBlockConnected(height int32, header *wire.BlockHeader, txns []*lbcutil.Tx) {
blockHash := header.BlockHash().String()
log.Printf("Block connected: %v (%d) %v", blockHash, height, header.Timestamp)
if cmd := *run; len(cmd) != 0 {
cmd = strings.ReplaceAll(cmd, "%s", blockHash)
err := execCustomCommand(cmd)
if err != nil {
log.Printf("ERROR: execCustomCommand: %s", err)
}
}
if len(*stratum) > 0 && len(*stratumPass) > 0 {
err := stratumUpdateBlock(*stratum, *stratumPass, *coinid, blockHash)
if err != nil {
log.Printf("ERROR: stratumUpdateBlock: %s", err)
}
}
}
func execCustomCommand(cmd string) error {
strs := strings.Split(cmd, " ")
path, err := exec.LookPath(strs[0])
if errors.Is(err, exec.ErrDot) {
err = nil
}
if err != nil {
return err
}
c := exec.Command(path, strs[1:]...)
c.Stdout = os.Stdout
return c.Run()
}
func stratumUpdateBlock(stratum, stratumPass, coinid, blockHash string) error {
addr, err := net.ResolveTCPAddr("tcp", stratum)
if err != nil {
return fmt.Errorf("can't resolve addr: %w", err)
}
conn, err := net.DialTCP("tcp", nil, addr)
if err != nil {
return fmt.Errorf("can't dial tcp: %w", err)
}
defer conn.Close()
msg := fmt.Sprintf(`{"id":1,"method":"mining.update_block","params":[%q,%s,%q]}`,
stratumPass, coinid, blockHash)
_, err = conn.Write([]byte(msg))
if err != nil {
return fmt.Errorf("can't write message: %w", err)
}
return nil
}
func main() { func main() {
flag.Parse() flag.Parse()
// Setup notification handler ntfnHandlers := rpcclient.NotificationHandlers{
b := newBridge(*stratumServer, *stratumPass, *coinid) OnFilteredBlockConnected: onFilteredBlockConnected,
if len(*run) > 0 {
// Check if ccommand exists.
strs := strings.Split(*run, " ")
cmd := strs[0]
_, err := exec.LookPath(cmd)
if err != nil {
log.Fatalf("ERROR: %s not found: %s", cmd, err)
}
b.customCmd = *run
} }
// Start the eventt handler. // Connect to local lbcd RPC server using websockets.
go b.start() lbcdHomeDir := lbcutil.AppDataDir("lbcd", false)
certs, err := ioutil.ReadFile(filepath.Join(lbcdHomeDir, "rpc.cert"))
if err != nil {
log.Fatalf("can't read lbcd certificate: %s", err)
}
connCfg := &rpcclient.ConnConfig{
Host: *rpcserver,
Endpoint: "ws",
User: *rpcuser,
Pass: *rpcpass,
Certificates: certs,
DisableTLS: *notls,
}
client, err := rpcclient.New(connCfg, &ntfnHandlers)
if err != nil {
log.Fatalf("can't create rpc client: %s", err)
}
// Adaptater receives lbcd notifications, and emit events. // Register for block connect and disconnect notifications.
adpt := adapter{b} if err = client.NotifyBlocks(); err != nil {
log.Fatalf("can't register block notification: %s", err)
}
log.Printf("NotifyBlocks: Registration Complete")
client := newLbcdClient(*rpcserver, *rpcuser, *rpcpass, *notls, adpt) // Get the current block count.
blockCount, err := client.GetBlockCount()
go func() { if err != nil {
err := <-b.errorc log.Fatalf("can't get block count: %s", err)
log.Fatalf("ERROR: %s", err) }
client.Shutdown() log.Printf("Block count: %d", blockCount)
}()
// Wait until the client either shuts down gracefully (or the user // Wait until the client either shuts down gracefully (or the user
// terminates the process with Ctrl+C). // terminates the process with Ctrl+C).

View file

@ -1,56 +0,0 @@
package main
import (
"context"
"fmt"
"net"
)
type stratumClient struct {
server string
passwd string
coinid string
conn *net.TCPConn
}
func newStratumClient(server, passwd, coinid string) *stratumClient {
return &stratumClient{
server: server,
}
}
func (c *stratumClient) dial() error {
addr, err := net.ResolveTCPAddr("tcp", c.server)
if err != nil {
return fmt.Errorf("resolve tcp addr: %w", err)
}
conn, err := net.DialTCP("tcp", nil, addr)
if err != nil {
return fmt.Errorf("dial tcp: %w", err)
}
c.conn = conn
return nil
}
func (c *stratumClient) send(ctx context.Context, msg string) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
_, err := c.conn.Write([]byte(msg))
return err
}
func stratumUpdateBlockMsg(stratumPass, coinid, blockHash string) string {
return fmt.Sprintf(`{"id":1,"method":"mining.update_block","params":[%q,%s,%q]}`,
stratumPass, coinid, blockHash)
}

View file

@ -774,8 +774,7 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) {
tries := 10 tries := 10
for i := 0; tries == 0 || i < tries; i++ { for i := 0; tries == 0 || i < tries; i++ {
bodyReader := bytes.NewReader(jReq.marshalledJSON) bodyReader := bytes.NewReader(jReq.marshalledJSON)
var httpReq *http.Request httpReq, err := http.NewRequest("POST", url, bodyReader)
httpReq, err = http.NewRequest("POST", url, bodyReader)
if err != nil { if err != nil {
jReq.responseChan <- &Response{result: nil, err: err} jReq.responseChan <- &Response{result: nil, err: err}
return return
@ -787,8 +786,7 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) {
} }
// Configure basic access authorization. // Configure basic access authorization.
var user, pass string user, pass, err := c.config.getAuth()
user, pass, err = c.config.getAuth()
if err != nil { if err != nil {
jReq.responseChan <- &Response{result: nil, err: err} jReq.responseChan <- &Response{result: nil, err: err}
return return

View file

@ -536,10 +536,9 @@ func (r FutureSendToAddressResult) Receive() (*chainhash.Hash, error) {
// returned instance. // returned instance.
// //
// See SendToAddress for the blocking version and more details. // See SendToAddress for the blocking version and more details.
func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amount, func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amount) FutureSendToAddressResult {
addrType *string) FutureSendToAddressResult {
addr := address.EncodeAddress() addr := address.EncodeAddress()
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), addrType, nil, nil) cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), nil, nil)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -551,9 +550,8 @@ func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amou
// //
// NOTE: This function requires to the wallet to be unlocked. See the // NOTE: This function requires to the wallet to be unlocked. See the
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount, func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount) (*chainhash.Hash, error) {
addrType *string) (*chainhash.Hash, error) { return c.SendToAddressAsync(address, amount).Receive()
return c.SendToAddressAsync(address, amount, addrType).Receive()
} }
// SendToAddressCommentAsync returns an instance of a type that can be used to // SendToAddressCommentAsync returns an instance of a type that can be used to
@ -562,12 +560,12 @@ func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount,
// //
// See SendToAddressComment for the blocking version and more details. // See SendToAddressComment for the blocking version and more details.
func (c *Client) SendToAddressCommentAsync(address btcutil.Address, func (c *Client) SendToAddressCommentAsync(address btcutil.Address,
amount btcutil.Amount, addrType *string, comment string, amount btcutil.Amount, comment,
commentTo string) FutureSendToAddressResult { commentTo string) FutureSendToAddressResult {
addr := address.EncodeAddress() addr := address.EncodeAddress()
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), addrType, cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), &comment,
&comment, &commentTo) &commentTo)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -583,10 +581,9 @@ func (c *Client) SendToAddressCommentAsync(address btcutil.Address,
// //
// NOTE: This function requires to the wallet to be unlocked. See the // NOTE: This function requires to the wallet to be unlocked. See the
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendToAddressComment(address btcutil.Address, amount btcutil.Amount, func (c *Client) SendToAddressComment(address btcutil.Address, amount btcutil.Amount, comment, commentTo string) (*chainhash.Hash, error) {
addrType *string, comment, commentTo string) (*chainhash.Hash, error) { return c.SendToAddressCommentAsync(address, amount, comment,
return c.SendToAddressCommentAsync(address, amount, addrType, commentTo).Receive()
comment, commentTo).Receive()
} }
// FutureSendFromResult is a future promise to deliver the result of a // FutureSendFromResult is a future promise to deliver the result of a
@ -618,11 +615,10 @@ func (r FutureSendFromResult) Receive() (*chainhash.Hash, error) {
// returned instance. // returned instance.
// //
// See SendFrom for the blocking version and more details. // See SendFrom for the blocking version and more details.
func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address, func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount) FutureSendFromResult {
amount btcutil.Amount, addrType *string) FutureSendFromResult {
addr := toAddress.EncodeAddress() addr := toAddress.EncodeAddress()
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(), nil, cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(), nil,
addrType, nil, nil) nil, nil)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -634,8 +630,8 @@ func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address,
// //
// NOTE: This function requires to the wallet to be unlocked. See the // NOTE: This function requires to the wallet to be unlocked. See the
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, addrType *string) (*chainhash.Hash, error) { func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount) (*chainhash.Hash, error) {
return c.SendFromAsync(fromAccount, toAddress, amount, addrType).Receive() return c.SendFromAsync(fromAccount, toAddress, amount).Receive()
} }
// SendFromMinConfAsync returns an instance of a type that can be used to get // SendFromMinConfAsync returns an instance of a type that can be used to get
@ -643,12 +639,10 @@ func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount
// the returned instance. // the returned instance.
// //
// See SendFromMinConf for the blocking version and more details. // See SendFromMinConf for the blocking version and more details.
func (c *Client) SendFromMinConfAsync(fromAccount string, func (c *Client) SendFromMinConfAsync(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, minConfirms int) FutureSendFromResult {
toAddress btcutil.Address, amount btcutil.Amount,
minConfirms int, addrType *string) FutureSendFromResult {
addr := toAddress.EncodeAddress() addr := toAddress.EncodeAddress()
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(), cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(),
&minConfirms, addrType, nil, nil) &minConfirms, nil, nil)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -661,10 +655,9 @@ func (c *Client) SendFromMinConfAsync(fromAccount string,
// //
// NOTE: This function requires to the wallet to be unlocked. See the // NOTE: This function requires to the wallet to be unlocked. See the
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address, func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, minConfirms int) (*chainhash.Hash, error) {
amount btcutil.Amount, minConfirms int, addrType *string) (*chainhash.Hash, error) {
return c.SendFromMinConfAsync(fromAccount, toAddress, amount, return c.SendFromMinConfAsync(fromAccount, toAddress, amount,
minConfirms, addrType).Receive() minConfirms).Receive()
} }
// SendFromCommentAsync returns an instance of a type that can be used to get // SendFromCommentAsync returns an instance of a type that can be used to get
@ -674,11 +667,11 @@ func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address,
// See SendFromComment for the blocking version and more details. // See SendFromComment for the blocking version and more details.
func (c *Client) SendFromCommentAsync(fromAccount string, func (c *Client) SendFromCommentAsync(fromAccount string,
toAddress btcutil.Address, amount btcutil.Amount, minConfirms int, toAddress btcutil.Address, amount btcutil.Amount, minConfirms int,
addrType *string, comment, commentTo string) FutureSendFromResult { comment, commentTo string) FutureSendFromResult {
addr := toAddress.EncodeAddress() addr := toAddress.EncodeAddress()
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(), cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(),
&minConfirms, addrType, &comment, &commentTo) &minConfirms, &comment, &commentTo)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -694,11 +687,11 @@ func (c *Client) SendFromCommentAsync(fromAccount string,
// NOTE: This function requires to the wallet to be unlocked. See the // NOTE: This function requires to the wallet to be unlocked. See the
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendFromComment(fromAccount string, toAddress btcutil.Address, func (c *Client) SendFromComment(fromAccount string, toAddress btcutil.Address,
amount btcutil.Amount, minConfirms int, addrType *string, amount btcutil.Amount, minConfirms int,
comment, commentTo string) (*chainhash.Hash, error) { comment, commentTo string) (*chainhash.Hash, error) {
return c.SendFromCommentAsync(fromAccount, toAddress, amount, return c.SendFromCommentAsync(fromAccount, toAddress, amount,
minConfirms, addrType, comment, commentTo).Receive() minConfirms, comment, commentTo).Receive()
} }
// FutureSendManyResult is a future promise to deliver the result of a // FutureSendManyResult is a future promise to deliver the result of a
@ -735,7 +728,7 @@ func (c *Client) SendManyAsync(fromAccount string, amounts map[btcutil.Address]b
for addr, amount := range amounts { for addr, amount := range amounts {
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC() convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
} }
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, nil, nil, nil) cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, nil, nil)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -758,14 +751,14 @@ func (c *Client) SendMany(fromAccount string, amounts map[btcutil.Address]btcuti
// See SendManyMinConf for the blocking version and more details. // See SendManyMinConf for the blocking version and more details.
func (c *Client) SendManyMinConfAsync(fromAccount string, func (c *Client) SendManyMinConfAsync(fromAccount string,
amounts map[btcutil.Address]btcutil.Amount, amounts map[btcutil.Address]btcutil.Amount,
minConfirms int, addrType *string) FutureSendManyResult { minConfirms int) FutureSendManyResult {
convertedAmounts := make(map[string]float64, len(amounts)) convertedAmounts := make(map[string]float64, len(amounts))
for addr, amount := range amounts { for addr, amount := range amounts {
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC() convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
} }
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts,
&minConfirms, nil, addrType) &minConfirms, nil)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -780,10 +773,9 @@ func (c *Client) SendManyMinConfAsync(fromAccount string,
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendManyMinConf(fromAccount string, func (c *Client) SendManyMinConf(fromAccount string,
amounts map[btcutil.Address]btcutil.Amount, amounts map[btcutil.Address]btcutil.Amount,
minConfirms int, addrType *string) (*chainhash.Hash, error) { minConfirms int) (*chainhash.Hash, error) {
return c.SendManyMinConfAsync(fromAccount, amounts, minConfirms, return c.SendManyMinConfAsync(fromAccount, amounts, minConfirms).Receive()
addrType).Receive()
} }
// SendManyCommentAsync returns an instance of a type that can be used to get // SendManyCommentAsync returns an instance of a type that can be used to get
@ -793,14 +785,14 @@ func (c *Client) SendManyMinConf(fromAccount string,
// See SendManyComment for the blocking version and more details. // See SendManyComment for the blocking version and more details.
func (c *Client) SendManyCommentAsync(fromAccount string, func (c *Client) SendManyCommentAsync(fromAccount string,
amounts map[btcutil.Address]btcutil.Amount, minConfirms int, amounts map[btcutil.Address]btcutil.Amount, minConfirms int,
addrType *string, comment string) FutureSendManyResult { comment string) FutureSendManyResult {
convertedAmounts := make(map[string]float64, len(amounts)) convertedAmounts := make(map[string]float64, len(amounts))
for addr, amount := range amounts { for addr, amount := range amounts {
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC() convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
} }
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts,
&minConfirms, &comment, addrType) &minConfirms, &comment)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -816,10 +808,10 @@ func (c *Client) SendManyCommentAsync(fromAccount string,
// WalletPassphrase function for more details. // WalletPassphrase function for more details.
func (c *Client) SendManyComment(fromAccount string, func (c *Client) SendManyComment(fromAccount string,
amounts map[btcutil.Address]btcutil.Amount, minConfirms int, amounts map[btcutil.Address]btcutil.Amount, minConfirms int,
addrType *string, comment string) (*chainhash.Hash, error) { comment string) (*chainhash.Hash, error) {
return c.SendManyCommentAsync(fromAccount, amounts, minConfirms, return c.SendManyCommentAsync(fromAccount, amounts, minConfirms,
addrType, comment).Receive() comment).Receive()
} }
// ************************* // *************************
@ -1143,8 +1135,8 @@ func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
// function on the returned instance. // function on the returned instance.
// //
// See GetRawChangeAddress for the blocking version and more details. // See GetRawChangeAddress for the blocking version and more details.
func (c *Client) GetRawChangeAddressAsync(account *string) FutureGetRawChangeAddressResult { func (c *Client) GetRawChangeAddressAsync(account string) FutureGetRawChangeAddressResult {
cmd := btcjson.NewGetRawChangeAddressCmd(account) cmd := btcjson.NewGetRawChangeAddressCmd(&account)
result := FutureGetRawChangeAddressResult{ result := FutureGetRawChangeAddressResult{
network: c.chainParams, network: c.chainParams,
responseChannel: c.SendCmd(cmd), responseChannel: c.SendCmd(cmd),
@ -1155,7 +1147,7 @@ func (c *Client) GetRawChangeAddressAsync(account *string) FutureGetRawChangeAdd
// GetRawChangeAddress returns a new address for receiving change that will be // GetRawChangeAddress returns a new address for receiving change that will be
// associated with the provided account. Note that this is only for raw // associated with the provided account. Note that this is only for raw
// transactions and NOT for normal use. // transactions and NOT for normal use.
func (c *Client) GetRawChangeAddress(account *string) (btcutil.Address, error) { func (c *Client) GetRawChangeAddress(account string) (btcutil.Address, error) {
return c.GetRawChangeAddressAsync(account).Receive() return c.GetRawChangeAddressAsync(account).Receive()
} }
@ -1234,7 +1226,7 @@ func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
// the returned instance. // the returned instance.
// //
// See GetAccountAddress for the blocking version and more details. // See GetAccountAddress for the blocking version and more details.
func (c *Client) GetAccountAddressAsync(account *string) FutureGetAccountAddressResult { func (c *Client) GetAccountAddressAsync(account string) FutureGetAccountAddressResult {
cmd := btcjson.NewGetAccountAddressCmd(account) cmd := btcjson.NewGetAccountAddressCmd(account)
result := FutureGetAccountAddressResult{ result := FutureGetAccountAddressResult{
network: c.chainParams, network: c.chainParams,
@ -1245,7 +1237,7 @@ func (c *Client) GetAccountAddressAsync(account *string) FutureGetAccountAddress
// GetAccountAddress returns the current Bitcoin address for receiving payments // GetAccountAddress returns the current Bitcoin address for receiving payments
// to the specified account. // to the specified account.
func (c *Client) GetAccountAddress(account *string) (btcutil.Address, error) { func (c *Client) GetAccountAddress(account string) (btcutil.Address, error) {
return c.GetAccountAddressAsync(account).Receive() return c.GetAccountAddressAsync(account).Receive()
} }
@ -1325,7 +1317,7 @@ func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error)
// function on the returned instance. // function on the returned instance.
// //
// See GetAddressesByAccount for the blocking version and more details. // See GetAddressesByAccount for the blocking version and more details.
func (c *Client) GetAddressesByAccountAsync(account *string) FutureGetAddressesByAccountResult { func (c *Client) GetAddressesByAccountAsync(account string) FutureGetAddressesByAccountResult {
cmd := btcjson.NewGetAddressesByAccountCmd(account) cmd := btcjson.NewGetAddressesByAccountCmd(account)
result := FutureGetAddressesByAccountResult{ result := FutureGetAddressesByAccountResult{
network: c.chainParams, network: c.chainParams,
@ -1336,7 +1328,7 @@ func (c *Client) GetAddressesByAccountAsync(account *string) FutureGetAddressesB
// GetAddressesByAccount returns the list of addresses associated with the // GetAddressesByAccount returns the list of addresses associated with the
// passed account. // passed account.
func (c *Client) GetAddressesByAccount(account *string) ([]btcutil.Address, error) { func (c *Client) GetAddressesByAccount(account string) ([]btcutil.Address, error) {
return c.GetAddressesByAccountAsync(account).Receive() return c.GetAddressesByAccountAsync(account).Receive()
} }
@ -1717,7 +1709,7 @@ func (r FutureGetReceivedByAccountResult) Receive() (btcutil.Amount, error) {
// function on the returned instance. // function on the returned instance.
// //
// See GetReceivedByAccount for the blocking version and more details. // See GetReceivedByAccount for the blocking version and more details.
func (c *Client) GetReceivedByAccountAsync(account *string) FutureGetReceivedByAccountResult { func (c *Client) GetReceivedByAccountAsync(account string) FutureGetReceivedByAccountResult {
cmd := btcjson.NewGetReceivedByAccountCmd(account, nil) cmd := btcjson.NewGetReceivedByAccountCmd(account, nil)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -1727,7 +1719,7 @@ func (c *Client) GetReceivedByAccountAsync(account *string) FutureGetReceivedByA
// //
// See GetReceivedByAccountMinConf to override the minimum number of // See GetReceivedByAccountMinConf to override the minimum number of
// confirmations. // confirmations.
func (c *Client) GetReceivedByAccount(account *string) (btcutil.Amount, error) { func (c *Client) GetReceivedByAccount(account string) (btcutil.Amount, error) {
return c.GetReceivedByAccountAsync(account).Receive() return c.GetReceivedByAccountAsync(account).Receive()
} }
@ -1736,8 +1728,8 @@ func (c *Client) GetReceivedByAccount(account *string) (btcutil.Amount, error) {
// function on the returned instance. // function on the returned instance.
// //
// See GetReceivedByAccountMinConf for the blocking version and more details. // See GetReceivedByAccountMinConf for the blocking version and more details.
func (c *Client) GetReceivedByAccountMinConfAsync(account *string, minConfirms *int) FutureGetReceivedByAccountResult { func (c *Client) GetReceivedByAccountMinConfAsync(account string, minConfirms int) FutureGetReceivedByAccountResult {
cmd := btcjson.NewGetReceivedByAccountCmd(account, minConfirms) cmd := btcjson.NewGetReceivedByAccountCmd(account, &minConfirms)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -1746,7 +1738,7 @@ func (c *Client) GetReceivedByAccountMinConfAsync(account *string, minConfirms *
// confirmations. // confirmations.
// //
// See GetReceivedByAccount to use the default minimum number of confirmations. // See GetReceivedByAccount to use the default minimum number of confirmations.
func (c *Client) GetReceivedByAccountMinConf(account *string, minConfirms *int) (btcutil.Amount, error) { func (c *Client) GetReceivedByAccountMinConf(account string, minConfirms int) (btcutil.Amount, error) {
return c.GetReceivedByAccountMinConfAsync(account, minConfirms).Receive() return c.GetReceivedByAccountMinConfAsync(account, minConfirms).Receive()
} }
@ -2062,14 +2054,14 @@ func (r FutureRescanBlockchainResult) Receive() (*btcjson.RescanBlockchainResult
// returned instance. // returned instance.
// //
// See RescanBlockchain for the blocking version and more details. // See RescanBlockchain for the blocking version and more details.
func (c *Client) RescanBlockchainAsync(startHeight *int32, stopHeight *int32) FutureRescanBlockchainResult { func (c *Client) RescanBlockchainAsync(startHeight *int64, stopHeight *int64) FutureRescanBlockchainResult {
cmd := btcjson.NewRescanBlockchainCmd(startHeight, stopHeight) cmd := btcjson.NewRescanBlockchainCmd(startHeight, stopHeight)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
// RescanBlockchain rescans the local blockchain for wallet related // RescanBlockchain rescans the local blockchain for wallet related
// transactions from the startHeight to the the inclusive stopHeight. // transactions from the startHeight to the the inclusive stopHeight.
func (c *Client) RescanBlockchain(startHeight *int32, stopHeight *int32) (*btcjson.RescanBlockchainResult, error) { func (c *Client) RescanBlockchain(startHeight *int64, stopHeight *int64) (*btcjson.RescanBlockchainResult, error) {
return c.RescanBlockchainAsync(startHeight, stopHeight).Receive() return c.RescanBlockchainAsync(startHeight, stopHeight).Receive()
} }

View file

@ -21,7 +21,6 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -152,7 +151,6 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"getblockcount": handleGetBlockCount, "getblockcount": handleGetBlockCount,
"getblockhash": handleGetBlockHash, "getblockhash": handleGetBlockHash,
"getblockheader": handleGetBlockHeader, "getblockheader": handleGetBlockHeader,
"getblockstats": handleGetBlockStats,
"getblocktemplate": handleGetBlockTemplate, "getblocktemplate": handleGetBlockTemplate,
"getcfilter": handleGetCFilter, "getcfilter": handleGetCFilter,
"getcfilterheader": handleGetCFilterHeader, "getcfilterheader": handleGetCFilterHeader,
@ -1580,405 +1578,6 @@ func handleGetChainTips(s *rpcServer, cmd interface{}, closeChan <-chan struct{}
return results, nil return results, nil
} }
// handleGetBlockStats implements the getblockstats command.
func handleGetBlockStats(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetBlockStatsCmd)
// Check whether a block height or hash was provided.
blockHeight, ok := c.HashOrHeight.Value.(int)
var hash *chainhash.Hash
var err error
if ok {
// Block height was provided.
hash, err = s.cfg.Chain.BlockHashByHeight(int32(blockHeight))
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCOutOfRange,
Message: "Block number out of range",
}
}
} else {
// Block hash was provided.
hashString := c.HashOrHeight.Value.(string)
hash, err = chainhash.NewHashFromStr(hashString)
if err != nil {
return nil, rpcDecodeHexError(hashString)
}
// Get the block height from chain.
blockHeightByHash, err := s.cfg.Chain.BlockHeightByHash(hash)
if err != nil {
context := "Failed to obtain block height"
return nil, internalRPCError(err.Error(), context)
}
blockHeight = int(blockHeightByHash)
}
// Load block bytes from the database.
var blkBytes []byte
err = s.cfg.DB.View(func(dbTx database.Tx) error {
var err error
blkBytes, err = dbTx.FetchBlock(hash)
return err
})
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCBlockNotFound,
Message: "Block not found",
}
}
// Deserialize the block.
blk, err := btcutil.NewBlockFromBytes(blkBytes)
if err != nil {
context := "Failed to deserialize block"
return nil, internalRPCError(err.Error(), context)
}
var selectedStats []string
if c.Stats != nil {
selectedStats = *c.Stats
}
// Create a set of selected stats to facilitate queries.
statsSet := make(map[string]bool)
for _, value := range selectedStats {
statsSet[value] = true
}
// Return all stats if an empty array was provided.
allStats := len(selectedStats) == 0
calcFees := statsSet["avgfee"] || statsSet["avgfeerate"] || statsSet["maxfee"] || statsSet["maxfeerate"] ||
statsSet["medianfee"] || statsSet["totalfee"] || statsSet["feerate_percentiles"]
if calcFees && s.cfg.TxIndex == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCNoTxInfo,
Message: "The transaction index must be " +
"enabled to obtain fee statistics " +
"(specify --txindex)",
}
}
txs := blk.Transactions()
txCount := len(txs)
var inputCount, outputCount int
var totalOutputValue int64
// Create a map of transaction statistics.
txStats := make([]map[string]interface{}, txCount)
for i, tx := range txs {
size := tx.MsgTx().SerializeSize()
witnessSize := size - tx.MsgTx().SerializeSizeStripped()
weight := int64(tx.MsgTx().SerializeSizeStripped()*4 + witnessSize)
var fee, feeRate int64
if (calcFees || allStats) && s.cfg.TxIndex != nil && !blockchain.IsCoinBaseTx(tx.MsgTx()) {
fee, err = calculateFee(tx, s.cfg.TxIndex, s.cfg.DB)
if err != nil {
context := "Failed to calculate fees"
return nil, internalRPCError(err.Error(), context)
}
if weight != 0 {
feeRate = fee * 4 / weight
}
}
segwit := tx.HasWitness()
txStats[i] = map[string]interface{}{"tx": tx, "fee": fee, "size": int64(size),
"feeRate": feeRate, "weight": weight, "segwit": segwit}
inputCount += len(tx.MsgTx().TxIn)
outputCount += len(tx.MsgTx().TxOut)
// Coinbase is excluded from the total output.
if !blockchain.IsCoinBase(tx) {
for _, txOut := range tx.MsgTx().TxOut {
totalOutputValue += txOut.Value
}
}
}
var totalFees, minFee, maxFee, minFeeRate, maxFeeRate, segwitCount,
segwitWeight, totalWeight, totalSize, minSize, maxSize, segwitSize int64
if txCount > 1 {
minFee = txStats[1]["fee"].(int64)
minFeeRate = txStats[1]["feeRate"].(int64)
}
for i := 0; i < len(txStats); i++ {
var fee, feeRate int64
tx := txStats[i]["tx"].(*btcutil.Tx)
if !blockchain.IsCoinBaseTx(tx.MsgTx()) {
// Fee statistics.
fee = txStats[i]["fee"].(int64)
feeRate = txStats[i]["feeRate"].(int64)
if minFee > fee {
minFee = fee
}
if maxFee < fee {
maxFee = fee
}
if minFeeRate > feeRate {
minFeeRate = feeRate
}
if maxFeeRate < feeRate {
maxFeeRate = feeRate
}
totalFees += txStats[i]["fee"].(int64)
// Segwit statistics.
if txStats[i]["segwit"].(bool) {
segwitCount++
segwitSize += txStats[i]["size"].(int64)
segwitWeight += txStats[i]["weight"].(int64)
}
// Size statistics.
size := txStats[i]["size"].(int64)
if minSize == 0 {
minSize = size
}
if maxSize < size {
maxSize = size
} else if minSize > size {
minSize = size
}
totalSize += txStats[i]["size"].(int64)
totalWeight += txStats[i]["weight"].(int64)
}
}
var avgFee, avgFeeRate, avgSize int64
if txCount > 1 {
avgFee = totalFees / int64(txCount-1)
}
if totalWeight != 0 {
avgFeeRate = totalFees * 4 / totalWeight
}
if txCount > 1 {
avgSize = totalSize / int64(txCount-1)
}
subsidy := blockchain.CalcBlockSubsidy(int32(blockHeight), s.cfg.ChainParams)
medianStat := func(stat string) int64 {
size := len(txStats) - 1
if size == 0 {
return 0
}
statArray := make([]int64, size)
// Start with the second element to ignore entry associated with coinbase.
for i, stats := range txStats[1:] {
statArray[i] = stats[stat].(int64)
}
sort.Slice(statArray, func(i, j int) bool {
return statArray[i] < statArray[j]
})
if size%2 == 0 {
return (statArray[size/2-1] + statArray[size/2]) / 2
}
return statArray[size/2]
}
var medianFee int64
if totalFees > 0 {
medianFee = medianStat("fee")
} else {
medianFee = 0
}
medianSize := medianStat("size")
// Calculate feerate percentiles.
var feeratePercentiles []int64
if allStats || calcFees {
// Sort by feerate.
sort.Slice(txStats, func(i, j int) bool {
return txStats[i]["feeRate"].(int64) < txStats[j]["feeRate"].(int64)
})
totalWeight := float64(totalWeight)
// Find 10th, 25th, 50th, 75th and 90th percentile weight units.
weights := []float64{
totalWeight / 10, totalWeight / 4, totalWeight / 2,
(totalWeight * 3) / 4, (totalWeight * 9) / 10}
var cumulativeWeight int64
feeratePercentiles = make([]int64, len(weights))
nextPercentileIndex := 0
for i := 0; i < len(txStats); i++ {
cumulativeWeight += txStats[i]["weight"].(int64)
for nextPercentileIndex < len(weights) && float64(cumulativeWeight) >= weights[nextPercentileIndex] {
feeratePercentiles[nextPercentileIndex] = txStats[i]["feeRate"].(int64)
nextPercentileIndex++
}
}
// Fill any remaining percentiles with the last value.
for i := nextPercentileIndex; i < len(weights); i++ {
feeratePercentiles[i] = txStats[len(txStats)-1]["feeRate"].(int64)
}
}
var blockHash string
if allStats || statsSet["blockhash"] {
blockHash = blk.Hash().String()
}
medianTime, err := medianBlockTime(blk.Hash(), s.cfg.Chain)
if err != nil {
context := "Failed to obtain block median time"
return nil, internalRPCError(err.Error(), context)
}
resultMap := map[string]int64{
"avgfee": avgFee,
"avgfeerate": avgFeeRate,
"avgtxsize": avgSize,
"height": int64(blockHeight),
"ins": int64(inputCount - 1), // Coinbase input is not included.
"maxfee": maxFee,
"maxfeerate": maxFeeRate,
"maxtxsize": maxSize,
"medianfee": medianFee,
"mediantime": medianTime.Unix(),
"mediantxsize": medianSize,
"minfee": minFee,
"minfeerate": minFeeRate,
"mintxsize": minSize,
"outs": int64(outputCount),
"swtotal_size": segwitSize,
"swtotal_weight": segwitWeight,
"swtxs": segwitCount,
"subsidy": subsidy,
"time": blk.MsgBlock().Header.Timestamp.Unix(),
"total_out": totalOutputValue,
"total_size": totalSize,
"total_weight": totalWeight,
"totalfee": totalFees,
"txs": int64(len(txs)),
"utxo_increase": int64(outputCount - (inputCount - 1)),
}
// This function determines whether a statistic goes into the
// final result, except for blockhash and feerate_percentiles
// which are handled separately.
resultFilter := func(stat string) *int64 {
if allStats && s.cfg.TxIndex == nil {
// There are no fee statistics to send.
excludedStats := []string{"avgfee", "avgfeerate", "maxfee", "maxfeerate", "medianfee", "minfee", "minfeerate"}
for _, excluded := range excludedStats {
if stat == excluded {
return nil
}
}
}
if allStats || statsSet[stat] {
if value, ok := resultMap[stat]; ok {
return &value
}
}
return nil
}
result := &btcjson.GetBlockStatsResult{
AverageFee: resultFilter("avgfee"),
AverageFeeRate: resultFilter("avgfeerate"),
AverageTxSize: resultFilter("avgtxsize"),
FeeratePercentiles: &feeratePercentiles,
Hash: &blockHash,
Height: resultFilter("height"),
Ins: resultFilter("ins"),
MaxFee: resultFilter("maxfee"),
MaxFeeRate: resultFilter("maxfeerate"),
MaxTxSize: resultFilter("maxtxsize"),
MedianFee: resultFilter("medianfee"),
MedianTime: resultFilter("mediantime"),
MedianTxSize: resultFilter("mediantxsize"),
MinFee: resultFilter("minfee"),
MinFeeRate: resultFilter("minfeerate"),
MinTxSize: resultFilter("mintxsize"),
Outs: resultFilter("outs"),
SegWitTotalSize: resultFilter("swtotal_size"),
SegWitTotalWeight: resultFilter("swtotal_weight"),
SegWitTxs: resultFilter("swtxs"),
Subsidy: resultFilter("subsidy"),
Time: resultFilter("time"),
TotalOut: resultFilter("total_out"),
TotalSize: resultFilter("total_size"),
TotalWeight: resultFilter("total_weight"),
TotalFee: resultFilter("totalfee"),
Txs: resultFilter("txs"),
UTXOIncrease: resultFilter("utxo_increase"),
UTXOSizeIncrease: resultFilter("utxo_size_inc"),
}
return result, nil
}
// calculateFee returns the fee of a transaction.
func calculateFee(tx *btcutil.Tx, txIndex *indexers.TxIndex, db database.DB) (int64, error) {
var inValue, outValue int64
for _, input := range tx.MsgTx().TxIn {
prevTxHash := input.PreviousOutPoint.Hash
// Look up the location of the previous transaction in the index.
blockRegion, err := txIndex.TxBlockRegion(&prevTxHash)
if err != nil {
context := "Failed to retrieve transaction location"
return 0, internalRPCError(err.Error(), context)
}
if blockRegion == nil {
return 0, rpcNoTxInfoError(&prevTxHash)
}
// Load the raw transaction bytes from the database.
var txBytes []byte
err = db.View(func(dbTx database.Tx) error {
var err error
txBytes, err = dbTx.FetchBlockRegion(blockRegion)
return err
})
if err != nil {
return 0, rpcNoTxInfoError(&prevTxHash)
}
var msgTx wire.MsgTx
err = msgTx.Deserialize(bytes.NewReader(txBytes))
if err != nil {
context := "Failed to deserialize transaction"
return 0, internalRPCError(err.Error(), context)
}
prevOutValue := msgTx.TxOut[input.PreviousOutPoint.Index].Value
inValue += prevOutValue
}
for _, output := range tx.MsgTx().TxOut {
outValue += output.Value
}
fee := inValue - outValue
return fee, nil
}
// medianBlockTime returns the median time of a block and its 10 previous blocks
// as per BIP113.
func medianBlockTime(blockHash *chainhash.Hash, chain *blockchain.BlockChain) (*time.Time, error) {
blockTimes := make([]time.Time, 0)
currentHash := blockHash
for i := 0; i < 11; i++ {
header, err := chain.HeaderByHash(currentHash)
if err != nil {
return nil, err
}
blockTimes = append(blockTimes, header.Timestamp)
genesisPrevBlock, _ := chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
if header.PrevBlock.IsEqual(genesisPrevBlock) {
// This is the genesis block so there's no need to iterate further.
break
}
currentHash = &header.PrevBlock
}
sort.Slice(blockTimes, func(i, j int) bool {
return blockTimes[i].Before(blockTimes[j])
})
return &blockTimes[len(blockTimes)/2], nil
}
// encodeTemplateID encodes the passed details into an ID that can be used to // encodeTemplateID encodes the passed details into an ID that can be used to
// uniquely identify a block template. // uniquely identify a block template.
func encodeTemplateID(prevHash *chainhash.Hash, lastGenerated time.Time) string { func encodeTemplateID(prevHash *chainhash.Hash, lastGenerated time.Time) string {
@ -2368,6 +1967,7 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
if useCoinbaseValue { if useCoinbaseValue {
reply.CoinbaseAux = gbtCoinbaseAux reply.CoinbaseAux = gbtCoinbaseAux
reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value
} else { } else {
// Ensure the template has a valid payment address associated // Ensure the template has a valid payment address associated
// with it when a full coinbase is requested. // with it when a full coinbase is requested.
@ -2400,9 +2000,6 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
reply.CoinbaseTxn = &resultTx reply.CoinbaseTxn = &resultTx
} }
// Return coinbasevalue anyway as lbrycrd and bitcoind do.
reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value
return &reply, nil return &reply, nil
} }

View file

@ -200,43 +200,6 @@ var helpDescsEnUS = map[string]string{
"getblockchaininforesult-softforks": "The status of the super-majority soft-forks", "getblockchaininforesult-softforks": "The status of the super-majority soft-forks",
"getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0", "getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0",
// GetBlockStatsCmd help.
"getblockstats--synopsis": "Returns statistics about a block given its hash or height. --txindex must be enabled for fee and feerate statistics.",
"getblockstats-hashorheight": "The hash or height of the block",
"hashorheight-value": "The hash or height of the block",
"getblockstats-stats": "Selected statistics",
// GetBlockStatsResult help.
"getblockstatsresult-avgfee": "The average fee in the block",
"getblockstatsresult-avgfeerate": "The average feerate in the block (in satoshis per virtual byte)",
"getblockstatsresult-avgtxsize": "The average transaction size in the block",
"getblockstatsresult-blockhash": "The block hash",
"getblockstatsresult-feerate_percentiles": "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
"getblockstatsresult-height": "The block height",
"getblockstatsresult-ins": "The number of inputs (excluding coinbase)",
"getblockstatsresult-maxfee": "Maxium fee in the block",
"getblockstatsresult-maxfeerate": "Maximum feerate in the block (in satoshis per virtual byte)",
"getblockstatsresult-maxtxsize": "Maximum transaction size",
"getblockstatsresult-medianfee": "Truncated median fee",
"getblockstatsresult-mediantime": "The median time from the block and its previous 10 blocks (BIP113)",
"getblockstatsresult-mediantxsize": "Truncated median transaction size",
"getblockstatsresult-minfee": "Minimum fee in the block",
"getblockstatsresult-minfeerate": "Minimum feerate in the block (in satoshis per virtual byte)",
"getblockstatsresult-mintxsize": "Minimum transaction size",
"getblockstatsresult-outs": "The number of outputs",
"getblockstatsresult-subsidy": "The block subsidy",
"getblockstatsresult-swtotal_size": "Total size of all segwit transactions in the block (excluding coinbase)",
"getblockstatsresult-swtotal_weight": "Total weight of all segwit transactions in the block (excluding coinbase)",
"getblockstatsresult-swtxs": "The number of segwit transactions in the block (excluding coinbase)",
"getblockstatsresult-time": "The block time",
"getblockstatsresult-total_out": "Total amount in all outputs (excluding coinbase)",
"getblockstatsresult-total_size": "Total size of all transactions (excluding coinbase)",
"getblockstatsresult-total_weight": "Total weight of all transactions (excluding coinbase)",
"getblockstatsresult-totalfee": "The total of fees",
"getblockstatsresult-txs": "The number of transactions (excluding coinbase)",
"getblockstatsresult-utxo_increase": "The increase/decrease in the number of unspent outputs",
"getblockstatsresult-utxo_size_inc": "The increase/decrease in size for the utxo index",
// SoftForkDescription help. // SoftForkDescription help.
"softforkdescription-reject": "The current activation status of the softfork", "softforkdescription-reject": "The current activation status of the softfork",
"softforkdescription-version": "The block version that signals enforcement of this softfork", "softforkdescription-version": "The block version that signals enforcement of this softfork",
@ -954,7 +917,6 @@ var rpcResultTypes = map[string][]interface{}{
"getblockcount": {(*int64)(nil)}, "getblockcount": {(*int64)(nil)},
"getblockhash": {(*string)(nil)}, "getblockhash": {(*string)(nil)},
"getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)}, "getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},
"getblockstats": {(*btcjson.GetBlockStatsResult)(nil)},
"getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil}, "getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil},
"getcfilter": {(*string)(nil)}, "getcfilter": {(*string)(nil)},
"getcfilterheader": {(*string)(nil)}, "getcfilterheader": {(*string)(nil)},

View file

@ -206,7 +206,7 @@ func StripClaimScriptPrefix(script []byte) []byte {
return script[cs.Size:] return script[cs.Size:]
} }
const illegalChars = "=&#:$%?/;\\\b\n\t\r\x00" const illegalChars = "=&#:*$%?/;\\\b\n\t\r\x00"
func AllClaimsAreSane(script []byte, enforceSoftFork bool) error { func AllClaimsAreSane(script []byte, enforceSoftFork bool) error {
cs, err := ExtractClaimScript(script) cs, err := ExtractClaimScript(script)