Compare commits

..

1 commit

Author SHA1 Message Date
Roy Lee
70d0d8f86f txscript: validate claimscript size 2022-08-30 13:18:58 -07:00
39 changed files with 591 additions and 1961 deletions

View file

@ -22,17 +22,36 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup go ${{ env.GO_VERSION }} - name: setup go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5 uses: actions/setup-go@v2
with: with:
go-version: '${{ env.GO_VERSION }}' go-version: '${{ env.GO_VERSION }}'
- name: checkout source - name: checkout source
uses: actions/checkout@v5 uses: actions/checkout@v2
- name: compile code - name: compile code
run: go install -v ./... run: go install -v ./...
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v8 uses: golangci/golangci-lint-action@v2
with: with:
version: latest # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
# Optional: if set to true then the action will use pre-installed Go.
skip-go-installation: true
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true

View file

@ -49,7 +49,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- -
name: Upload artifacts name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: lbcd-${{ github.sha }} name: lbcd-${{ github.sha }}
path: | path: |

View file

@ -1,17 +1,71 @@
formatters: linters-settings:
enable: depguard:
- gofmt list-type: blacklist
- goimports packages:
settings: # logging is allowed only by logutils.Log, logrus
gci: # is allowed to use only in logutils package
sections: - github.com/sirupsen/logrus
- prefix(github.com/golangci/golangci-lint) packages-with-error-message:
goimports: - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log"
local-prefixes: dupl:
- github.com/golangci/golangci-lint threshold: 100
funlen:
lines: 100
statements: 50
gci:
local-prefixes: github.com/golangci/golangci-lint
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- whyNoLint
- wrapperFunc
gocyclo:
min-complexity: 15
goimports:
local-prefixes: github.com/golangci/golangci-lint
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks:
- argument
- case
- condition
- return
govet:
check-shadowing: true
settings:
printf:
funcs:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
linters: linters:
default: none disable-all: true
enable: enable:
- asciicheck - asciicheck
- bodyclose - bodyclose
@ -21,6 +75,7 @@ linters:
# - dupl # - dupl
# - errcheck # - errcheck
# - exhaustive # - exhaustive
- exportloopref
# - funlen # - funlen
# - gochecknoglobals # - gochecknoglobals
# - gochecknoinits # - gochecknoinits
@ -31,6 +86,8 @@ linters:
# - godot # - godot
# - godox # - godox
# - goerr113 # - goerr113
- gofmt
- goimports
# - gomnd # - gomnd
- goprintffuncname - goprintffuncname
# - gosec # - gosec
@ -60,91 +117,36 @@ linters:
# - varcheck # - varcheck
# - whitespace # - whitespace
# - wsl # - wsl
exclusions:
paths:
- test/testdata_etc
- internal/cache
- internal/renameio
- internal/robustio
rules:
- path: _test\.go
linters:
- gomnd
- path: pkg/golinters/errcheck.go
text: "SA1019: errCfg.Exclude is deprecated: use ExcludeFunctions instead"
- path: pkg/commands/run.go
text: "SA1019: lsc.Errcheck.Exclude is deprecated: use ExcludeFunctions instead"
# TODO must be removed after the release of the next version (v1.41.0) issues:
- path: pkg/commands/run.go # Excluding configuration per-path, per-linter, per-text and per-source
linters: exclude-rules:
- path: _test\.go
linters:
- gomnd - gomnd
# TODO must be removed after the release of the next version (v1.41.0)
- path: pkg/golinters/nolintlint/nolintlint.go
linters:
- gomnd
# TODO must be removed after the release of the next version (v1.41.0)
- path: pkg/printers/tab.go
linters:
- gomnd
settings:
govet:
enable:
- shadow
settings:
printf:
funcs:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
lll:
line-length: 140
depguard:
rules:
main:
list-mode: strict
allow:
- $gostd
- github.com/
- golang.org/x/crypto/ripemd160
deny:
- pkg: github.com/sirupsen/logrus
desc: "logging is allowed only by logutils.Log"
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- whyNoLint
- wrapperFunc
gocyclo:
min-complexity: 15
misspell:
locale: US
mnd:
checks:
- argument
- case
- condition
- return
nolintlint:
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
version: "2" - path: pkg/golinters/errcheck.go
text: "SA1019: errCfg.Exclude is deprecated: use ExcludeFunctions instead"
- path: pkg/commands/run.go
text: "SA1019: lsc.Errcheck.Exclude is deprecated: use ExcludeFunctions instead"
# TODO must be removed after the release of the next version (v1.41.0)
- path: pkg/commands/run.go
linters:
- gomnd
# TODO must be removed after the release of the next version (v1.41.0)
- path: pkg/golinters/nolintlint/nolintlint.go
linters:
- gomnd
# TODO must be removed after the release of the next version (v1.41.0)
- path: pkg/printers/tab.go
linters:
- gomnd
run:
skip-dirs:
- test/testdata_etc
- internal/cache
- internal/renameio
- internal/robustio

View file

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

View file

@ -9,7 +9,7 @@
Software stack developed by LBRY teams has been all migrated to **lbcd**. Software stack developed by LBRY teams has been all migrated to **lbcd**.
We're working with exchanges and pool operators to migrate from **lbrycrd** to **lbcd**. We're working with exchanges and pool oerators to migrate from **lbrycrd** to **lbcd**.
If you're integrating with **lbcd+lbcwallet**, please check the Wiki for current [supported RPCs](wiki/RPC-availability). If you're integrating with **lbcd+lbcwallet**, please check the Wiki for current [supported RPCs](wiki/RPC-availability).
@ -37,16 +37,6 @@ $ go install github.com/lbryio/lbcd@latest
$ go install github.com/lbryio/lbcd/cmd/lbcctl@latest $ go install github.com/lbryio/lbcd/cmd/lbcctl@latest
``` ```
## Building from Source
To build **lbcd** from source, clone the repository and use the `go build` command:
``` sh
$ git clone https://github.com/LBRYFoundation/lbcd.git
$ cd lbcd
$ go build .
```
## Usage ## Usage
Default application folder `${LBCDDIR}`: Default application folder `${LBCDDIR}`:

View file

@ -1394,8 +1394,8 @@ func (b *BlockChain) BlockAttributesByHash(hash *chainhash.Hash, prevHash *chain
attrs *BlockAttributes, best *BestState, err error) { attrs *BlockAttributes, best *BestState, err error) {
best = b.BestSnapshot() best = b.BestSnapshot()
node := b.index.LookupNode(hash) node := b.index.LookupNode(hash)
if node == nil { if node == nil || !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s not found", hash) str := fmt.Sprintf("block %s is not in the main chain", hash)
return nil, best, errNotInMainChain(str) return nil, best, errNotInMainChain(str)
} }
@ -1405,9 +1405,6 @@ func (b *BlockChain) BlockAttributesByHash(hash *chainhash.Hash, prevHash *chain
MedianTime: node.CalcPastMedianTime(), MedianTime: node.CalcPastMedianTime(),
ChainWork: node.workSum, ChainWork: node.workSum,
} }
if !b.bestChain.Contains(node) {
attrs.Confirmations = -1
}
// Populate prev block hash if there is one. // Populate prev block hash if there is one.
if node.height > 0 { if node.height > 0 {

View file

@ -67,7 +67,7 @@ type TransactionInput struct {
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command. // CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
type CreateRawTransactionCmd struct { type CreateRawTransactionCmd struct {
Inputs []TransactionInput Inputs []TransactionInput
Outputs map[string]interface{} `jsonrpcusage:"{\"address\":amount, \"data\":\"hex\", ...}"` Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
LockTime *int64 LockTime *int64
} }
@ -76,7 +76,7 @@ type CreateRawTransactionCmd struct {
// //
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent, // Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
// both gets interpreted as the empty slice. // both gets interpreted as the empty slice.
func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]interface{}, func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
lockTime *int64) *CreateRawTransactionCmd { lockTime *int64) *CreateRawTransactionCmd {
// to make sure we're serializing this to the empty list and not null, we // to make sure we're serializing this to the empty list and not null, we
// explicitly initialize the list // explicitly initialize the list
@ -85,7 +85,7 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]in
} }
return &CreateRawTransactionCmd{ return &CreateRawTransactionCmd{
Inputs: inputs, Inputs: inputs,
Outputs: outputs, Amounts: amounts,
LockTime: lockTime, LockTime: lockTime,
} }
} }

View file

@ -52,13 +52,13 @@ func TestChainSvrCmds(t *testing.T) {
txInputs := []btcjson.TransactionInput{ txInputs := []btcjson.TransactionInput{
{Txid: "123", Vout: 1}, {Txid: "123", Vout: 1},
} }
txOutputs := map[string]interface{}{"456": .0123} amounts := map[string]float64{"456": .0123}
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil) return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
unmarshalled: &btcjson.CreateRawTransactionCmd{ unmarshalled: &btcjson.CreateRawTransactionCmd{
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}}, Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
Outputs: map[string]interface{}{"456": .0123}, Amounts: map[string]float64{"456": .0123},
}, },
}, },
{ {
@ -67,13 +67,13 @@ func TestChainSvrCmds(t *testing.T) {
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`) return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
txOutputs := map[string]interface{}{"456": .0123} amounts := map[string]float64{"456": .0123}
return btcjson.NewCreateRawTransactionCmd(nil, txOutputs, nil) return btcjson.NewCreateRawTransactionCmd(nil, amounts, nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
unmarshalled: &btcjson.CreateRawTransactionCmd{ unmarshalled: &btcjson.CreateRawTransactionCmd{
Inputs: []btcjson.TransactionInput{}, Inputs: []btcjson.TransactionInput{},
Outputs: map[string]interface{}{"456": .0123}, Amounts: map[string]float64{"456": .0123},
}, },
}, },
{ {
@ -86,35 +86,16 @@ func TestChainSvrCmds(t *testing.T) {
txInputs := []btcjson.TransactionInput{ txInputs := []btcjson.TransactionInput{
{Txid: "123", Vout: 1}, {Txid: "123", Vout: 1},
} }
txOutputs := map[string]interface{}{"456": .0123} amounts := map[string]float64{"456": .0123}
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, btcjson.Int64(12312333333)) return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, btcjson.Int64(12312333333))
}, },
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
unmarshalled: &btcjson.CreateRawTransactionCmd{ unmarshalled: &btcjson.CreateRawTransactionCmd{
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}}, Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
Outputs: map[string]interface{}{"456": .0123}, Amounts: map[string]float64{"456": .0123},
LockTime: btcjson.Int64(12312333333), LockTime: btcjson.Int64(12312333333),
}, },
}, },
{
name: "createrawtransaction with data",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("createrawtransaction", `[{"txid":"123","vout":1}]`,
`{"data":"6a134920616d204672616374616c456e6372797074"}`)
},
staticCmd: func() interface{} {
txInputs := []btcjson.TransactionInput{
{Txid: "123", Vout: 1},
}
txOutputs := map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"}
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"data":"6a134920616d204672616374616c456e6372797074"}],"id":1}`,
unmarshalled: &btcjson.CreateRawTransactionCmd{
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
Outputs: map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"},
},
},
{ {
name: "fundrawtransaction - empty opts", name: "fundrawtransaction - empty opts",
newCmd: func() (i interface{}, e error) { newCmd: func() (i interface{}, e error) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -249,7 +249,7 @@ func GetDustThreshold(txOut *wire.TxOut) int64 {
totalSize += 107 totalSize += 107
} }
return int64(totalSize) return 3 * int64(totalSize)
} }
// IsDust returns whether or not the passed transaction output amount is // IsDust returns whether or not the passed transaction output amount is
@ -264,7 +264,7 @@ func IsDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool {
} }
// The output is considered dust if the cost to the network to spend the // The output is considered dust if the cost to the network to spend the
// coins is more than the minimum free transaction relay fee. // coins is more than 1/3 of the minimum free transaction relay fee.
// minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to // minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to
// convert to bytes. // convert to bytes.
// //
@ -273,7 +273,7 @@ func IsDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool {
// fee of 1000, this equates to values less than 546 satoshi being // fee of 1000, this equates to values less than 546 satoshi being
// considered dust. // considered dust.
// //
// The following is equivalent to (value/totalSize) * 1000 // The following is equivalent to (value/totalSize) * (1/3) * 1000
// without needing to do floating point math. // without needing to do floating point math.
if txOut.Value > dustCap { if txOut.Value > dustCap {
return false return false

View file

@ -233,14 +233,14 @@ func TestDust(t *testing.T) {
true, true,
}, },
{ {
"38 byte public key script with value 194", "38 byte public key script with value 584",
wire.TxOut{Value: 194, PkScript: pkScript}, wire.TxOut{Value: 584, PkScript: pkScript},
1000, 1000,
true, true,
}, },
{ {
"38 byte public key script with value 195", "38 byte public key script with value 585",
wire.TxOut{Value: 195, PkScript: pkScript}, wire.TxOut{Value: 585, PkScript: pkScript},
1000, 1000,
false, false,
}, },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -291,18 +291,13 @@ func (r FutureCreateRawTransactionResult) Receive() (*wire.MsgTx, error) {
// //
// See CreateRawTransaction for the blocking version and more details. // See CreateRawTransaction for the blocking version and more details.
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput, func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
outputs map[btcutil.Address]interface{}, lockTime *int64) FutureCreateRawTransactionResult { amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) FutureCreateRawTransactionResult {
convertedData := make(map[string]interface{}, len(outputs)) convertedAmts := make(map[string]float64, len(amounts))
for key, value := range outputs { for addr, amount := range amounts {
switch val := value.(type) { convertedAmts[addr.String()] = amount.ToBTC()
case btcutil.Amount:
convertedData[key.String()] = val.ToBTC()
case string:
convertedData[key.String()] = val
}
} }
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedData, lockTime) cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedAmts, lockTime)
return c.SendCmd(cmd) return c.SendCmd(cmd)
} }
@ -310,9 +305,9 @@ func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
// and sending to the provided addresses. If the inputs are either nil or an // and sending to the provided addresses. If the inputs are either nil or an
// empty slice, it is interpreted as an empty slice. // empty slice, it is interpreted as an empty slice.
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput, func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
outputs map[btcutil.Address]interface{}, lockTime *int64) (*wire.MsgTx, error) { amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) (*wire.MsgTx, error) {
return c.CreateRawTransactionAsync(inputs, outputs, lockTime).Receive() return c.CreateRawTransactionAsync(inputs, amounts, lockTime).Receive()
} }
// FutureSendRawTransactionResult is a future promise to deliver the result // FutureSendRawTransactionResult is a future promise to deliver the result

View file

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

View file

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

View file

@ -49,10 +49,10 @@ var helpDescsEnUS = map[string]string{
"The transaction inputs are not signed in the created transaction.\n" + "The transaction inputs are not signed in the created transaction.\n" +
"The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.", "The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.",
"createrawtransaction-inputs": "The inputs to the transaction", "createrawtransaction-inputs": "The inputs to the transaction",
"createrawtransaction-outputs": "JSON object with the destination addresses as keys and amounts as values", "createrawtransaction-amounts": "JSON object with the destination addresses as keys and amounts as values",
"createrawtransaction-outputs--key": "address or \"data\"", "createrawtransaction-amounts--key": "address",
"createrawtransaction-outputs--value": "value in BTC as floating point number or hex-encoded data for \"data\"", "createrawtransaction-amounts--value": "n.nnn",
"createrawtransaction-outputs--desc": "The destination address as the key and the amount in LBC as the value", "createrawtransaction-amounts--desc": "The destination address as the key and the amount in LBC as the value",
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs", "createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction", "createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
@ -200,43 +200,6 @@ var helpDescsEnUS = map[string]string{
"getblockchaininforesult-softforks": "The status of the super-majority soft-forks", "getblockchaininforesult-softforks": "The status of the super-majority soft-forks",
"getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0", "getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0",
// GetBlockStatsCmd help.
"getblockstats--synopsis": "Returns statistics about a block given its hash or height. --txindex must be enabled for fee and feerate statistics.",
"getblockstats-hashorheight": "The hash or height of the block",
"hashorheight-value": "The hash or height of the block",
"getblockstats-stats": "Selected statistics",
// GetBlockStatsResult help.
"getblockstatsresult-avgfee": "The average fee in the block",
"getblockstatsresult-avgfeerate": "The average feerate in the block (in satoshis per virtual byte)",
"getblockstatsresult-avgtxsize": "The average transaction size in the block",
"getblockstatsresult-blockhash": "The block hash",
"getblockstatsresult-feerate_percentiles": "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
"getblockstatsresult-height": "The block height",
"getblockstatsresult-ins": "The number of inputs (excluding coinbase)",
"getblockstatsresult-maxfee": "Maxium fee in the block",
"getblockstatsresult-maxfeerate": "Maximum feerate in the block (in satoshis per virtual byte)",
"getblockstatsresult-maxtxsize": "Maximum transaction size",
"getblockstatsresult-medianfee": "Truncated median fee",
"getblockstatsresult-mediantime": "The median time from the block and its previous 10 blocks (BIP113)",
"getblockstatsresult-mediantxsize": "Truncated median transaction size",
"getblockstatsresult-minfee": "Minimum fee in the block",
"getblockstatsresult-minfeerate": "Minimum feerate in the block (in satoshis per virtual byte)",
"getblockstatsresult-mintxsize": "Minimum transaction size",
"getblockstatsresult-outs": "The number of outputs",
"getblockstatsresult-subsidy": "The block subsidy",
"getblockstatsresult-swtotal_size": "Total size of all segwit transactions in the block (excluding coinbase)",
"getblockstatsresult-swtotal_weight": "Total weight of all segwit transactions in the block (excluding coinbase)",
"getblockstatsresult-swtxs": "The number of segwit transactions in the block (excluding coinbase)",
"getblockstatsresult-time": "The block time",
"getblockstatsresult-total_out": "Total amount in all outputs (excluding coinbase)",
"getblockstatsresult-total_size": "Total size of all transactions (excluding coinbase)",
"getblockstatsresult-total_weight": "Total weight of all transactions (excluding coinbase)",
"getblockstatsresult-totalfee": "The total of fees",
"getblockstatsresult-txs": "The number of transactions (excluding coinbase)",
"getblockstatsresult-utxo_increase": "The increase/decrease in the number of unspent outputs",
"getblockstatsresult-utxo_size_inc": "The increase/decrease in size for the utxo index",
// SoftForkDescription help. // SoftForkDescription help.
"softforkdescription-reject": "The current activation status of the softfork", "softforkdescription-reject": "The current activation status of the softfork",
"softforkdescription-version": "The block version that signals enforcement of this softfork", "softforkdescription-version": "The block version that signals enforcement of this softfork",
@ -954,7 +917,6 @@ var rpcResultTypes = map[string][]interface{}{
"getblockcount": {(*int64)(nil)}, "getblockcount": {(*int64)(nil)},
"getblockhash": {(*string)(nil)}, "getblockhash": {(*string)(nil)},
"getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)}, "getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},
"getblockstats": {(*btcjson.GetBlockStatsResult)(nil)},
"getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil}, "getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil},
"getcfilter": {(*string)(nil)}, "getcfilter": {(*string)(nil)},
"getcfilterheader": {(*string)(nil)}, "getcfilterheader": {(*string)(nil)},

View file

@ -104,11 +104,11 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
return nil, claimScriptError(ErrInvalidClaimNameScript, "expect OP_2DROP OP_DROP") return nil, claimScriptError(ErrInvalidClaimNameScript, "expect OP_2DROP OP_DROP")
} }
cs.Size = int(tokenizer.ByteIndex()) if tokenizer.ByteIndex() > MaxClaimScriptSize {
if cs.Size > MaxClaimScriptSize {
str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize) str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize)
return nil, claimScriptError(ErrInvalidClaimNameScript, str) return nil, claimScriptError(ErrInvalidClaimNameScript, str)
} }
cs.Size = int(tokenizer.ByteIndex())
return &cs, nil return &cs, nil
@ -150,11 +150,11 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_DROP") return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_DROP")
} }
cs.Size = int(tokenizer.ByteIndex()) if tokenizer.ByteIndex() > MaxClaimScriptSize {
if cs.Size > MaxClaimScriptSize {
str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize) str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize)
return nil, claimScriptError(ErrInvalidClaimSupportScript, str) return nil, claimScriptError(ErrInvalidClaimSupportScript, str)
} }
cs.Size = int(tokenizer.ByteIndex())
return &cs, nil return &cs, nil
@ -184,11 +184,11 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) return nil, claimScriptError(ErrInvalidClaimUpdateScript, str)
} }
cs.Size = int(tokenizer.ByteIndex()) if tokenizer.ByteIndex() > MaxClaimScriptSize {
if cs.Size > MaxClaimScriptSize {
str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize) str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize)
return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) return nil, claimScriptError(ErrInvalidClaimUpdateScript, str)
} }
cs.Size = int(tokenizer.ByteIndex())
return &cs, nil return &cs, 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)

40
upnp.go
View file

@ -69,18 +69,18 @@ type upnpNAT struct {
func Discover() (nat NAT, err error) { func Discover() (nat NAT, err error) {
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
if err != nil { if err != nil {
return nil, err return
} }
conn, err := net.ListenPacket("udp4", ":0") conn, err := net.ListenPacket("udp4", ":0")
if err != nil { if err != nil {
return nil, err return
} }
socket := conn.(*net.UDPConn) socket := conn.(*net.UDPConn)
defer socket.Close() defer socket.Close()
err = socket.SetDeadline(time.Now().Add(3 * time.Second)) err = socket.SetDeadline(time.Now().Add(3 * time.Second))
if err != nil { if err != nil {
return nil, err return
} }
st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
@ -95,7 +95,7 @@ func Discover() (nat NAT, err error) {
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
_, err = socket.WriteToUDP(message, ssdp) _, err = socket.WriteToUDP(message, ssdp)
if err != nil { if err != nil {
return nil, err return
} }
var n int var n int
n, _, err = socket.ReadFromUDP(answerBytes) n, _, err = socket.ReadFromUDP(answerBytes)
@ -124,16 +124,16 @@ func Discover() (nat NAT, err error) {
var serviceURL string var serviceURL string
serviceURL, err = getServiceURL(locURL) serviceURL, err = getServiceURL(locURL)
if err != nil { if err != nil {
return nil, err return
} }
var serviceIP string = getServiceIP(serviceURL) var serviceIP string = getServiceIP(serviceURL)
var ourIP string var ourIP string
ourIP, err = getOurIP(serviceIP) ourIP, err = getOurIP(serviceIP)
if err != nil { if err != nil {
return nil, err return
} }
nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP} nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
return nat, nil return
} }
err = errors.New("UPnP port discovery failed") err = errors.New("UPnP port discovery failed")
return nat, err return nat, err
@ -228,7 +228,7 @@ func getOurIP(serviceIP string) (ip string, err error) {
return ip.String(), nil return ip.String(), nil
} }
} }
return "", err return
} }
// getServiceURL parses the xml description at the given root url to find the // getServiceURL parses the xml description at the given root url to find the
@ -236,37 +236,37 @@ func getOurIP(serviceIP string) (ip string, err error) {
func getServiceURL(rootURL string) (url string, err error) { func getServiceURL(rootURL string) (url string, err error) {
r, err := http.Get(rootURL) r, err := http.Get(rootURL)
if err != nil { if err != nil {
return "", err return
} }
defer r.Body.Close() defer r.Body.Close()
if r.StatusCode >= 400 { if r.StatusCode >= 400 {
err = errors.New(fmt.Sprint(r.StatusCode)) err = errors.New(fmt.Sprint(r.StatusCode))
return "", err return
} }
var root root var root root
err = xml.NewDecoder(r.Body).Decode(&root) err = xml.NewDecoder(r.Body).Decode(&root)
if err != nil { if err != nil {
return "", err return
} }
a := &root.Device a := &root.Device
if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" { if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
err = errors.New("no InternetGatewayDevice") err = errors.New("no InternetGatewayDevice")
return "", err return
} }
b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1") b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
if b == nil { if b == nil {
err = errors.New("no WANDevice") err = errors.New("no WANDevice")
return "", err return
} }
c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1") c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
if c == nil { if c == nil {
err = errors.New("no WANConnectionDevice") err = errors.New("no WANConnectionDevice")
return "", err return
} }
d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1") d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
if d == nil { if d == nil {
err = errors.New("no WANIPConnection") err = errors.New("no WANIPConnection")
return "", err return
} }
url = combineURL(rootURL, d.ControlURL) url = combineURL(rootURL, d.ControlURL)
return url, err return url, err
@ -325,7 +325,7 @@ func soapRequest(url, function, message string) (replyXML []byte, err error) {
if r.StatusCode >= 400 { if r.StatusCode >= 400 {
err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function) err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
r = nil r = nil
return nil, err return
} }
var reply soapEnvelope var reply soapEnvelope
err = xml.NewDecoder(r.Body).Decode(&reply) err = xml.NewDecoder(r.Body).Decode(&reply)
@ -380,7 +380,7 @@ func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int
response, err := soapRequest(n.serviceURL, "AddPortMapping", message) response, err := soapRequest(n.serviceURL, "AddPortMapping", message)
if err != nil { if err != nil {
return 0, err return
} }
// TODO: check response to see if the port was forwarded // TODO: check response to see if the port was forwarded
@ -389,7 +389,7 @@ func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int
// codes here. // codes here.
mappedExternalPort = externalPort mappedExternalPort = externalPort
_ = response _ = response
return mappedExternalPort, nil return
} }
// DeletePortMapping implements the NAT interface by removing up a port forwarding // DeletePortMapping implements the NAT interface by removing up a port forwarding
@ -403,11 +403,11 @@ func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort
response, err := soapRequest(n.serviceURL, "DeletePortMapping", message) response, err := soapRequest(n.serviceURL, "DeletePortMapping", message)
if err != nil { if err != nil {
return err return
} }
// TODO: check response to see if the port was deleted // TODO: check response to see if the port was deleted
// log.Println(message, response) // log.Println(message, response)
_ = response _ = response
return nil return
} }