Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a0ff51b84a | ||
|
4c39a9842c | ||
|
f513fca6a7 | ||
|
6728bf4b08 | ||
|
979d643594 | ||
|
cbc4d489e8 | ||
|
987a533423 | ||
|
6bc9a2b4dd | ||
|
9bcd3d0591 | ||
|
2adfcd211d | ||
|
81ec217899 | ||
|
5acfa4c81b |
27 changed files with 1635 additions and 387 deletions
|
@ -35,35 +35,37 @@ 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"`
|
AverageFee *int64 `json:"avgfee,omitempty"`
|
||||||
AverageFeeRate int64 `json:"avgfeerate"`
|
AverageFeeRate *int64 `json:"avgfeerate,omitempty"`
|
||||||
AverageTxSize int64 `json:"avgtxsize"`
|
AverageTxSize *int64 `json:"avgtxsize,omitempty"`
|
||||||
FeeratePercentiles []int64 `json:"feerate_percentiles"`
|
FeeratePercentiles *[]int64 `json:"feerate_percentiles,omitempty"`
|
||||||
Hash string `json:"blockhash"`
|
Hash *string `json:"blockhash,omitempty"`
|
||||||
Height int64 `json:"height"`
|
Height *int64 `json:"height,omitempty"`
|
||||||
Ins int64 `json:"ins"`
|
Ins *int64 `json:"ins,omitempty"`
|
||||||
MaxFee int64 `json:"maxfee"`
|
MaxFee *int64 `json:"maxfee,omitempty"`
|
||||||
MaxFeeRate int64 `json:"maxfeerate"`
|
MaxFeeRate *int64 `json:"maxfeerate,omitempty"`
|
||||||
MaxTxSize int64 `json:"maxtxsize"`
|
MaxTxSize *int64 `json:"maxtxsize,omitempty"`
|
||||||
MedianFee int64 `json:"medianfee"`
|
MedianFee *int64 `json:"medianfee,omitempty"`
|
||||||
MedianTime int64 `json:"mediantime"`
|
MedianTime *int64 `json:"mediantime,omitempty"`
|
||||||
MedianTxSize int64 `json:"mediantxsize"`
|
MedianTxSize *int64 `json:"mediantxsize,omitempty"`
|
||||||
MinFee int64 `json:"minfee"`
|
MinFee *int64 `json:"minfee,omitempty"`
|
||||||
MinFeeRate int64 `json:"minfeerate"`
|
MinFeeRate *int64 `json:"minfeerate,omitempty"`
|
||||||
MinTxSize int64 `json:"mintxsize"`
|
MinTxSize *int64 `json:"mintxsize,omitempty"`
|
||||||
Outs int64 `json:"outs"`
|
Outs *int64 `json:"outs,omitempty"`
|
||||||
SegWitTotalSize int64 `json:"swtotal_size"`
|
SegWitTotalSize *int64 `json:"swtotal_size,omitempty"`
|
||||||
SegWitTotalWeight int64 `json:"swtotal_weight"`
|
SegWitTotalWeight *int64 `json:"swtotal_weight,omitempty"`
|
||||||
SegWitTxs int64 `json:"swtxs"`
|
SegWitTxs *int64 `json:"swtxs,omitempty"`
|
||||||
Subsidy int64 `json:"subsidy"`
|
Subsidy *int64 `json:"subsidy,omitempty"`
|
||||||
Time int64 `json:"time"`
|
Time *int64 `json:"time,omitempty"`
|
||||||
TotalOut int64 `json:"total_out"`
|
TotalOut *int64 `json:"total_out,omitempty"`
|
||||||
TotalSize int64 `json:"total_size"`
|
TotalSize *int64 `json:"total_size,omitempty"`
|
||||||
TotalWeight int64 `json:"total_weight"`
|
TotalWeight *int64 `json:"total_weight,omitempty"`
|
||||||
Txs int64 `json:"txs"`
|
TotalFee *int64 `json:"totalfee,omitempty"`
|
||||||
UTXOIncrease int64 `json:"utxo_increase"`
|
Txs *int64 `json:"txs,omitempty"`
|
||||||
UTXOSizeIncrease int64 `json:"utxo_size_inc"`
|
UTXOIncrease *int64 `json:"utxo_increase,omitempty"`
|
||||||
|
UTXOSizeIncrease *int64 `json:"utxo_size_inc,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBlockVerboseResultBase struct {
|
type GetBlockVerboseResultBase struct {
|
||||||
|
|
|
@ -176,12 +176,13 @@ 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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
|
@ -189,12 +190,13 @@ 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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
|
@ -215,8 +217,9 @@ 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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
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
|
||||||
|
@ -242,8 +245,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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
AddressType *string // must be one of legacy / p2pkh or p2sh-p2wkh / p2sh-segwit, or p2wkh / bech32
|
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
||||||
|
@ -259,7 +262,8 @@ 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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
|
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
|
||||||
|
@ -275,8 +279,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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
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
|
||||||
|
@ -284,7 +288,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,
|
||||||
|
@ -407,7 +411,8 @@ 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
|
||||||
|
@ -501,10 +506,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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
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
|
||||||
|
@ -562,6 +567,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -571,12 +577,15 @@ 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, minConf *int, comment, commentTo *string) *SendFromCmd {
|
func NewSendFromCmd(fromAccount, toAddress string, amount float64,
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
|
@ -587,6 +596,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,21 +605,24 @@ 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, minConf *int, comment *string) *SendManyCmd {
|
func NewSendManyCmd(fromAccount string, amounts map[string]float64,
|
||||||
|
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
|
||||||
Comment *string
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
CommentTo *string
|
Comment *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
|
||||||
|
@ -617,12 +630,14 @@ 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, comment, commentTo *string) *SendToAddressCmd {
|
func NewSendToAddressCmd(address string, amount float64, addrType *string,
|
||||||
|
comment, commentTo *string) *SendToAddressCmd {
|
||||||
return &SendToAddressCmd{
|
return &SendToAddressCmd{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Comment: comment,
|
AddressType: addrType,
|
||||||
CommentTo: commentTo,
|
Comment: comment,
|
||||||
|
CommentTo: commentTo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,8 +977,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 *int64 `jsonrpcdefault:"0"`
|
StartHeight *int32 `jsonrpcdefault:"0"`
|
||||||
StopHeight *int64 `jsonrpcdefault:"0"`
|
StopHeight *int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRescanBlockchainCmd returns a new instance which can be used to issue
|
// NewRescanBlockchainCmd returns a new instance which can be used to issue
|
||||||
|
@ -971,7 +986,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 *int64, stopHeight *int64) *RescanBlockchainCmd {
|
func NewRescanBlockchainCmd(startHeight *int32, stopHeight *int32) *RescanBlockchainCmd {
|
||||||
return &RescanBlockchainCmd{
|
return &RescanBlockchainCmd{
|
||||||
StartHeight: startHeight,
|
StartHeight: startHeight,
|
||||||
StopHeight: stopHeight,
|
StopHeight: stopHeight,
|
||||||
|
|
|
@ -287,11 +287,12 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("getaccountaddress", "acct")
|
return btcjson.NewCmd("getaccountaddress", "acct")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewGetAccountAddressCmd("acct")
|
return btcjson.NewGetAccountAddressCmd(btcjson.String("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: "acct",
|
Account: btcjson.String("acct"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -300,11 +301,12 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("getaddressesbyaccount", "acct")
|
return btcjson.NewCmd("getaddressesbyaccount", "acct")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewGetAddressesByAccountCmd("acct")
|
return btcjson.NewGetAddressesByAccountCmd(btcjson.String("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: "acct",
|
Account: btcjson.String("acct"),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -330,8 +332,9 @@ 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: nil,
|
Account: btcjson.String("default"),
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -344,8 +347,9 @@ 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("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -358,8 +362,9 @@ 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("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -383,7 +388,8 @@ 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: nil,
|
Account: btcjson.String("default"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -396,7 +402,8 @@ 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"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -409,7 +416,8 @@ 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: nil,
|
Account: btcjson.String("default"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -422,7 +430,8 @@ 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"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -431,11 +440,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("acct", nil)
|
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("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: "acct",
|
Account: btcjson.String("acct"),
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -445,11 +454,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("acct", btcjson.Int(6))
|
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("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: "acct",
|
Account: btcjson.String("acct"),
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -601,7 +610,8 @@ 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("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -614,7 +624,8 @@ 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("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -844,7 +855,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: nil,
|
Account: btcjson.String("default"),
|
||||||
Count: btcjson.Int(10),
|
Count: btcjson.Int(10),
|
||||||
From: btcjson.Int(0),
|
From: btcjson.Int(0),
|
||||||
IncludeWatchOnly: btcjson.Bool(false),
|
IncludeWatchOnly: btcjson.Bool(false),
|
||||||
|
@ -1002,7 +1013,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)
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, 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{
|
||||||
|
@ -1010,6 +1021,7 @@ 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,
|
||||||
},
|
},
|
||||||
|
@ -1020,7 +1032,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)
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, 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{
|
||||||
|
@ -1028,6 +1040,7 @@ 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,
|
||||||
},
|
},
|
||||||
|
@ -1035,37 +1048,59 @@ 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, "comment")
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||||
btcjson.String("comment"), nil)
|
nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy"],"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),
|
||||||
Comment: btcjson.String("comment"),
|
AddressType: btcjson.String("legacy"),
|
||||||
|
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, "comment", "commentto")
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy", "comment")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||||
btcjson.String("comment"), btcjson.String("commentto"))
|
btcjson.String("comment"), nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment","commentto"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","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"),
|
||||||
|
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"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment","commentto"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SendFromCmd{
|
||||||
|
FromAccount: "from",
|
||||||
|
ToAddress: "1Address",
|
||||||
|
Amount: 0.5,
|
||||||
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
Comment: btcjson.String("comment"),
|
Comment: btcjson.String("comment"),
|
||||||
CommentTo: btcjson.String("commentto"),
|
CommentTo: btcjson.String("commentto"),
|
||||||
},
|
},
|
||||||
|
@ -1077,13 +1112,14 @@ 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)
|
return btcjson.NewSendManyCmd("from", amounts, nil, 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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1094,30 +1130,50 @@ 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)
|
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil, 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, "comment")
|
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "legacy")
|
||||||
},
|
},
|
||||||
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("comment"))
|
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("legacy"), nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"comment"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"legacy"],"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"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1127,31 +1183,50 @@ 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)
|
return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, 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,
|
||||||
Comment: nil,
|
AddressType: btcjson.String("*"),
|
||||||
CommentTo: nil,
|
Comment: nil,
|
||||||
|
CommentTo: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sendtoaddress optional1",
|
name: "sendtoaddress optional1",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto")
|
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("comment"),
|
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), nil, nil)
|
||||||
|
},
|
||||||
|
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,"comment","commentto"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy","comment","commentto"],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendToAddressCmd{
|
unmarshalled: &btcjson.SendToAddressCmd{
|
||||||
Address: "1Address",
|
Address: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
Comment: btcjson.String("comment"),
|
AddressType: btcjson.String("legacy"),
|
||||||
CommentTo: btcjson.String("commentto"),
|
Comment: btcjson.String("comment"),
|
||||||
|
CommentTo: btcjson.String("commentto"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -174,6 +174,7 @@ 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 {
|
||||||
|
@ -288,7 +289,6 @@ 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 int64 `json:"start_height"`
|
StartHeight int32 `json:"start_height"`
|
||||||
StoptHeight int64 `json:"stop_height"`
|
StoptHeight int32 `json:"stop_height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignRawTransactionError models the data that contains script verification
|
// SignRawTransactionError models the data that contains script verification
|
||||||
|
|
|
@ -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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
||||||
|
|
|
@ -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", "acct", true)
|
return btcjson.NewCmd("exportwatchingwallet", btcjson.String("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: nil,
|
Account: btcjson.String("default"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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: nil,
|
Account: btcjson.String("default"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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: nil,
|
Account: btcjson.String("default"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,9 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
@ -249,17 +247,17 @@ func (ct *ClaimTrie) AppendBlock(temporary bool) error {
|
||||||
names = append(names, expirations...)
|
names = append(names, expirations...)
|
||||||
names = removeDuplicates(names)
|
names = removeDuplicates(names)
|
||||||
|
|
||||||
nhns := ct.makeNameHashNext(names, false, nil)
|
for _, name := range names {
|
||||||
for nhn := range nhns {
|
|
||||||
|
|
||||||
ct.merkleTrie.Update(nhn.Name, nhn.Hash, true)
|
hash, next := ct.nodeManager.Hash(name)
|
||||||
if nhn.Next <= 0 {
|
ct.merkleTrie.Update(name, hash, true)
|
||||||
|
if next <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
newName := normalization.NormalizeIfNecessary(nhn.Name, nhn.Next)
|
newName := normalization.NormalizeIfNecessary(name, next)
|
||||||
updateNames = append(updateNames, newName)
|
updateNames = append(updateNames, newName)
|
||||||
updateHeights = append(updateHeights, nhn.Next)
|
updateHeights = append(updateHeights, next)
|
||||||
}
|
}
|
||||||
if !temporary && len(updateNames) > 0 {
|
if !temporary && len(updateNames) > 0 {
|
||||||
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
|
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
|
||||||
|
@ -356,22 +354,29 @@ 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)
|
|
||||||
} else {
|
|
||||||
ct.claimLogger = nil
|
|
||||||
nhns = ct.makeNameHashNext(names, false, interrupt)
|
|
||||||
}
|
|
||||||
|
|
||||||
for nhn := range nhns {
|
ct.nodeManager.IterateNames(func(name []byte) bool {
|
||||||
ct.merkleTrie.Update(nhn.Name, nhn.Hash, false)
|
if interruptRequested(interrupt) {
|
||||||
if ct.claimLogger != nil {
|
return false
|
||||||
ct.claimLogger.LogName(nhn.Name)
|
}
|
||||||
|
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 {
|
||||||
|
for _, name := range names {
|
||||||
|
hash, _ := ct.nodeManager.Hash(name)
|
||||||
|
ct.merkleTrie.Update(name, hash, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MerkleHash returns the Merkle Hash of the claimTrie.
|
// MerkleHash returns the Merkle Hash of the claimTrie.
|
||||||
|
@ -437,12 +442,6 @@ 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
|
||||||
|
@ -452,53 +451,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|
85
claimtrie/node/cache.go
Normal file
85
claimtrie/node/cache.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
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()}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ 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 {
|
||||||
|
@ -30,31 +31,62 @@ 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) {
|
||||||
|
|
||||||
changes, err := nm.repo.LoadChanges(name)
|
n, changes, oldHeight := nm.cache.fetch(name, height)
|
||||||
if err != nil {
|
if n == nil {
|
||||||
return nil, errors.Wrap(err, "in load changes")
|
changes, err := nm.repo.LoadChanges(name)
|
||||||
}
|
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
|
||||||
|
@ -66,17 +98,13 @@ func (nm *BaseManager) node(name []byte) (*Node, error) {
|
||||||
return nm.NodeAt(nm.height, name)
|
return nm.NodeAt(nm.height, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNodeFromChanges returns a new Node constructed from the changes.
|
func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, height int32) (bool, error) {
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
n := New()
|
|
||||||
previous := changes[0].Height
|
|
||||||
count := len(changes)
|
count := len(changes)
|
||||||
|
if count == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
previous := changes[0].Height
|
||||||
|
|
||||||
for i, chg := range changes {
|
for i, chg := range changes {
|
||||||
if chg.Height < previous {
|
if chg.Height < previous {
|
||||||
|
@ -95,15 +123,37 @@ func (nm *BaseManager) newNodeFromChanges(changes []change.Change, height int32)
|
||||||
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 nil, errors.Wrap(err, "in apply change")
|
return false, errors.Wrap(err, "in apply change")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if count <= 0 {
|
if count <= 0 {
|
||||||
return nil, nil
|
// 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]
|
lastChange := changes[count-1]
|
||||||
return n.AdjustTo(lastChange.Height, height, lastChange.Name), nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
n := New()
|
||||||
|
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) {
|
||||||
|
@ -220,6 +270,7 @@ 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")
|
||||||
}
|
}
|
||||||
|
@ -255,6 +306,8 @@ 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
|
||||||
|
|
||||||
|
|
|
@ -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) *Node {
|
func (n *Node) AdjustTo(height, maxHeight int32, name []byte) {
|
||||||
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,7 +120,6 @@ func (n *Node) AdjustTo(height, maxHeight int32, name []byte) *Node {
|
||||||
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) {
|
||||||
|
@ -340,3 +339,28 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -110,5 +111,7 @@ func (nm *NormalizingManager) addNormalizationForkChangesIfNecessary(height int3
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nm.Manager.ClearCache()
|
||||||
nm.Manager.IterateNames(predicate)
|
nm.Manager.IterateNames(predicate)
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,8 @@ 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
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/btcjson"
|
"github.com/lbryio/lbcd/btcjson"
|
||||||
)
|
)
|
||||||
|
@ -133,6 +134,8 @@ 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)
|
||||||
|
@ -141,6 +144,16 @@ 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, "[") {
|
||||||
|
@ -150,7 +163,7 @@ func main() {
|
||||||
err)
|
err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println(dst.String())
|
fmt.Fprintln(output, dst.String())
|
||||||
|
|
||||||
} else if strings.HasPrefix(strResult, `"`) {
|
} else if strings.HasPrefix(strResult, `"`) {
|
||||||
var str string
|
var str string
|
||||||
|
@ -159,9 +172,9 @@ func main() {
|
||||||
err)
|
err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println(str)
|
fmt.Fprintln(output, str)
|
||||||
|
|
||||||
} else if strResult != "null" {
|
} else if strResult != "null" {
|
||||||
fmt.Println(strResult)
|
fmt.Fprintln(output, strResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
42
contrib/showminer.sh
Executable file
42
contrib/showminer.sh
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#! /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}
|
|
@ -13,12 +13,17 @@ 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) {
|
||||||
|
@ -133,13 +138,278 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +421,8 @@ 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.
|
||||||
btcdCfg := []string{"--rejectnonstd"}
|
// Enable transaction index to be able to fully test GetBlockStats
|
||||||
|
btcdCfg := []string{"--rejectnonstd", "--txindex"}
|
||||||
primaryHarness, err = rpctest.New(
|
primaryHarness, err = rpctest.New(
|
||||||
&chaincfg.SimNetParams, nil, btcdCfg, "",
|
&chaincfg.SimNetParams, nil, btcdCfg, "",
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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"
|
||||||
|
@ -512,6 +513,18 @@ 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
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
# lbcd Websockets Example
|
# lbcdbloknotify
|
||||||
|
|
||||||
This example shows how to use the rpcclient package to connect to a btcd RPC
|
This bridge program subscribes to lbcd's notifications over websockets using the rpcclient package.
|
||||||
server using TLS-secured websockets, register for block connected and block
|
Users can specify supported actions upon receiving this notifications.
|
||||||
disconnected notifications, and get the current block count.
|
|
||||||
|
|
||||||
## Running the Example
|
## Building(or Running) the Program
|
||||||
|
|
||||||
The first step is to clone the lbcd package:
|
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:
|
||||||
|
@ -29,18 +35,31 @@ $ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the program:
|
Running the program:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go run . -stratumpass <STRATUM PASSWD> -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD>
|
# Send stratum mining.update_block mesage upon receving block connected notifiations.
|
||||||
|
$ go run . -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD> --notls -stratum <STRATUM SERVER> -stratumpass <STRATUM PASSWD>
|
||||||
|
|
||||||
2022/01/10 23:16:21 NotifyBlocks: Registration Complete
|
2022/01/10 23:16:21 Current block count: 1093112
|
||||||
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.
|
||||||
|
|
20
rpcclient/examples/lbcdblocknotify/adapter.go
Normal file
20
rpcclient/examples/lbcdblocknotify/adapter.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
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}
|
||||||
|
}
|
172
rpcclient/examples/lbcdblocknotify/bridge.go
Normal file
172
rpcclient/examples/lbcdblocknotify/bridge.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
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()
|
||||||
|
}
|
53
rpcclient/examples/lbcdblocknotify/lbcdclient.go
Normal file
53
rpcclient/examples/lbcdblocknotify/lbcdclient.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,133 +1,63 @@
|
||||||
// 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 (
|
||||||
coinid = flag.String("coinid", "1425", "Coin ID")
|
lbcdHomeDir = lbcutil.AppDataDir("lbcd", false)
|
||||||
stratum = flag.String("stratum", "", "Stratum server")
|
defaultCert = filepath.Join(lbcdHomeDir, "rpc.cert")
|
||||||
stratumPass = flag.String("stratumpass", "", "Stratum server password")
|
)
|
||||||
rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server")
|
var (
|
||||||
rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username")
|
coinid = flag.String("coinid", "1425", "Coin ID")
|
||||||
rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password")
|
stratumServer = flag.String("stratum", "", "Stratum server")
|
||||||
notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled")
|
stratumPass = flag.String("stratumpass", "", "Stratum server password")
|
||||||
run = flag.String("run", "", "Run custom shell command")
|
rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server")
|
||||||
|
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()
|
||||||
|
|
||||||
ntfnHandlers := rpcclient.NotificationHandlers{
|
// Setup notification handler
|
||||||
OnFilteredBlockConnected: onFilteredBlockConnected,
|
b := newBridge(*stratumServer, *stratumPass, *coinid)
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to local lbcd RPC server using websockets.
|
// Start the eventt handler.
|
||||||
lbcdHomeDir := lbcutil.AppDataDir("lbcd", false)
|
go b.start()
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register for block connect and disconnect notifications.
|
// Adaptater receives lbcd notifications, and emit events.
|
||||||
if err = client.NotifyBlocks(); err != nil {
|
adpt := adapter{b}
|
||||||
log.Fatalf("can't register block notification: %s", err)
|
|
||||||
}
|
|
||||||
log.Printf("NotifyBlocks: Registration Complete")
|
|
||||||
|
|
||||||
// Get the current block count.
|
client := newLbcdClient(*rpcserver, *rpcuser, *rpcpass, *notls, adpt)
|
||||||
blockCount, err := client.GetBlockCount()
|
|
||||||
if err != nil {
|
go func() {
|
||||||
log.Fatalf("can't get block count: %s", err)
|
err := <-b.errorc
|
||||||
}
|
log.Fatalf("ERROR: %s", err)
|
||||||
log.Printf("Block count: %d", blockCount)
|
client.Shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
// 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).
|
||||||
|
|
56
rpcclient/examples/lbcdblocknotify/stratumclient.go
Normal file
56
rpcclient/examples/lbcdblocknotify/stratumclient.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -774,7 +774,8 @@ 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)
|
||||||
httpReq, err := http.NewRequest("POST", url, bodyReader)
|
var httpReq *http.Request
|
||||||
|
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
|
||||||
|
@ -786,7 +787,8 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure basic access authorization.
|
// Configure basic access authorization.
|
||||||
user, pass, err := c.config.getAuth()
|
var user, pass string
|
||||||
|
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
|
||||||
|
|
|
@ -536,9 +536,10 @@ 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) FutureSendToAddressResult {
|
func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amount,
|
||||||
|
addrType *string) FutureSendToAddressResult {
|
||||||
addr := address.EncodeAddress()
|
addr := address.EncodeAddress()
|
||||||
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), nil, nil)
|
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), addrType, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,8 +551,9 @@ 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) (*chainhash.Hash, error) {
|
func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount,
|
||||||
return c.SendToAddressAsync(address, amount).Receive()
|
addrType *string) (*chainhash.Hash, error) {
|
||||||
|
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
|
||||||
|
@ -560,12 +562,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, comment,
|
amount btcutil.Amount, addrType *string, comment string,
|
||||||
commentTo string) FutureSendToAddressResult {
|
commentTo string) FutureSendToAddressResult {
|
||||||
|
|
||||||
addr := address.EncodeAddress()
|
addr := address.EncodeAddress()
|
||||||
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), &comment,
|
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), addrType,
|
||||||
&commentTo)
|
&comment, &commentTo)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,9 +583,10 @@ 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, comment, commentTo string) (*chainhash.Hash, error) {
|
func (c *Client) SendToAddressComment(address btcutil.Address, amount btcutil.Amount,
|
||||||
return c.SendToAddressCommentAsync(address, amount, comment,
|
addrType *string, comment, commentTo string) (*chainhash.Hash, error) {
|
||||||
commentTo).Receive()
|
return c.SendToAddressCommentAsync(address, amount, addrType,
|
||||||
|
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
|
||||||
|
@ -615,10 +618,11 @@ 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, amount btcutil.Amount) FutureSendFromResult {
|
func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address,
|
||||||
|
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,
|
||||||
nil, nil)
|
addrType, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,8 +634,8 @@ func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address, am
|
||||||
//
|
//
|
||||||
// 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) (*chainhash.Hash, error) {
|
func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, addrType *string) (*chainhash.Hash, error) {
|
||||||
return c.SendFromAsync(fromAccount, toAddress, amount).Receive()
|
return c.SendFromAsync(fromAccount, toAddress, amount, addrType).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
|
||||||
|
@ -639,10 +643,12 @@ 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, toAddress btcutil.Address, amount btcutil.Amount, minConfirms int) FutureSendFromResult {
|
func (c *Client) SendFromMinConfAsync(fromAccount string,
|
||||||
|
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, nil, nil)
|
&minConfirms, addrType, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,9 +661,10 @@ func (c *Client) SendFromMinConfAsync(fromAccount string, toAddress btcutil.Addr
|
||||||
//
|
//
|
||||||
// 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, amount btcutil.Amount, minConfirms int) (*chainhash.Hash, error) {
|
func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address,
|
||||||
|
amount btcutil.Amount, minConfirms int, addrType *string) (*chainhash.Hash, error) {
|
||||||
return c.SendFromMinConfAsync(fromAccount, toAddress, amount,
|
return c.SendFromMinConfAsync(fromAccount, toAddress, amount,
|
||||||
minConfirms).Receive()
|
minConfirms, addrType).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
|
||||||
|
@ -667,11 +674,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,
|
||||||
comment, commentTo string) FutureSendFromResult {
|
addrType *string, 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, &comment, &commentTo)
|
&minConfirms, addrType, &comment, &commentTo)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,11 +694,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,
|
amount btcutil.Amount, minConfirms int, addrType *string,
|
||||||
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, comment, commentTo).Receive()
|
minConfirms, addrType, 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
|
||||||
|
@ -728,7 +735,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)
|
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, nil, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,14 +758,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) FutureSendManyResult {
|
minConfirms int, addrType *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, nil)
|
&minConfirms, nil, addrType)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -773,9 +780,10 @@ 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) (*chainhash.Hash, error) {
|
minConfirms int, addrType *string) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
return c.SendManyMinConfAsync(fromAccount, amounts, minConfirms).Receive()
|
return c.SendManyMinConfAsync(fromAccount, amounts, minConfirms,
|
||||||
|
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
|
||||||
|
@ -785,14 +793,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,
|
||||||
comment string) FutureSendManyResult {
|
addrType *string, 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)
|
&minConfirms, &comment, addrType)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -808,10 +816,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,
|
||||||
comment string) (*chainhash.Hash, error) {
|
addrType *string, comment string) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
return c.SendManyCommentAsync(fromAccount, amounts, minConfirms,
|
return c.SendManyCommentAsync(fromAccount, amounts, minConfirms,
|
||||||
comment).Receive()
|
addrType, comment).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// *************************
|
// *************************
|
||||||
|
@ -1135,8 +1143,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),
|
||||||
|
@ -1147,7 +1155,7 @@ func (c *Client) GetRawChangeAddressAsync(account string) FutureGetRawChangeAddr
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1226,7 +1234,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,
|
||||||
|
@ -1237,7 +1245,7 @@ func (c *Client) GetAccountAddressAsync(account string) FutureGetAccountAddressR
|
||||||
|
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1317,7 +1325,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,
|
||||||
|
@ -1328,7 +1336,7 @@ func (c *Client) GetAddressesByAccountAsync(account string) FutureGetAddressesBy
|
||||||
|
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1709,7 +1717,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)
|
||||||
}
|
}
|
||||||
|
@ -1719,7 +1727,7 @@ func (c *Client) GetReceivedByAccountAsync(account string) FutureGetReceivedByAc
|
||||||
//
|
//
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1728,8 +1736,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1738,7 +1746,7 @@ func (c *Client) GetReceivedByAccountMinConfAsync(account string, minConfirms in
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2054,14 +2062,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 *int64, stopHeight *int64) FutureRescanBlockchainResult {
|
func (c *Client) RescanBlockchainAsync(startHeight *int32, stopHeight *int32) 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 *int64, stopHeight *int64) (*btcjson.RescanBlockchainResult, error) {
|
func (c *Client) RescanBlockchain(startHeight *int32, stopHeight *int32) (*btcjson.RescanBlockchainResult, error) {
|
||||||
return c.RescanBlockchainAsync(startHeight, stopHeight).Receive()
|
return c.RescanBlockchainAsync(startHeight, stopHeight).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
405
rpcserver.go
405
rpcserver.go
|
@ -21,6 +21,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -151,6 +152,7 @@ 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,
|
||||||
|
@ -1578,6 +1580,405 @@ 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 {
|
||||||
|
@ -1967,7 +2368,6 @@ 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.
|
||||||
|
@ -2000,6 +2400,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,43 @@ 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",
|
||||||
|
@ -917,6 +954,7 @@ 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)},
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue